editor_tests.rs

    1use super::*;
    2use crate::{
    3    JoinLines,
    4    code_context_menus::CodeContextMenu,
    5    edit_prediction_tests::FakeEditPredictionProvider,
    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, CloseOtherItems, 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();
   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(),
   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_beginning_of_line_with_cursor_between_line_start_and_indent(cx: &mut TestAppContext) {
 1906    init_test(cx, |_| {});
 1907
 1908    let move_to_beg = MoveToBeginningOfLine {
 1909        stop_at_soft_wraps: true,
 1910        stop_at_indent: true,
 1911    };
 1912
 1913    let editor = cx.add_window(|window, cx| {
 1914        let buffer = MultiBuffer::build_simple("    hello\nworld", cx);
 1915        build_editor(buffer, window, cx)
 1916    });
 1917
 1918    _ = editor.update(cx, |editor, window, cx| {
 1919        // test cursor between line_start and indent_start
 1920        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1921            s.select_display_ranges([
 1922                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3)
 1923            ]);
 1924        });
 1925
 1926        // cursor should move to line_start
 1927        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1928        assert_eq!(
 1929            editor.selections.display_ranges(cx),
 1930            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1931        );
 1932
 1933        // cursor should move to indent_start
 1934        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1935        assert_eq!(
 1936            editor.selections.display_ranges(cx),
 1937            &[DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 4)]
 1938        );
 1939
 1940        // cursor should move to back to line_start
 1941        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1942        assert_eq!(
 1943            editor.selections.display_ranges(cx),
 1944            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1945        );
 1946    });
 1947}
 1948
 1949#[gpui::test]
 1950fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 1951    init_test(cx, |_| {});
 1952
 1953    let editor = cx.add_window(|window, cx| {
 1954        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 1955        build_editor(buffer, window, cx)
 1956    });
 1957    _ = editor.update(cx, |editor, window, cx| {
 1958        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1959            s.select_display_ranges([
 1960                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 1961                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 1962            ])
 1963        });
 1964        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1965        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 1966
 1967        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1968        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 1969
 1970        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1971        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1972
 1973        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1974        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1975
 1976        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1977        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 1978
 1979        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1980        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1981
 1982        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1983        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 1984
 1985        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1986        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1987
 1988        editor.move_right(&MoveRight, window, cx);
 1989        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1990        assert_selection_ranges(
 1991            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 1992            editor,
 1993            cx,
 1994        );
 1995
 1996        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1997        assert_selection_ranges(
 1998            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 1999            editor,
 2000            cx,
 2001        );
 2002
 2003        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 2004        assert_selection_ranges(
 2005            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 2006            editor,
 2007            cx,
 2008        );
 2009    });
 2010}
 2011
 2012#[gpui::test]
 2013fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 2014    init_test(cx, |_| {});
 2015
 2016    let editor = cx.add_window(|window, cx| {
 2017        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 2018        build_editor(buffer, window, cx)
 2019    });
 2020
 2021    _ = editor.update(cx, |editor, window, cx| {
 2022        editor.set_wrap_width(Some(140.0.into()), cx);
 2023        assert_eq!(
 2024            editor.display_text(cx),
 2025            "use one::{\n    two::three::\n    four::five\n};"
 2026        );
 2027
 2028        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2029            s.select_display_ranges([
 2030                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 2031            ]);
 2032        });
 2033
 2034        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2035        assert_eq!(
 2036            editor.selections.display_ranges(cx),
 2037            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 2038        );
 2039
 2040        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2041        assert_eq!(
 2042            editor.selections.display_ranges(cx),
 2043            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2044        );
 2045
 2046        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2047        assert_eq!(
 2048            editor.selections.display_ranges(cx),
 2049            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2050        );
 2051
 2052        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2053        assert_eq!(
 2054            editor.selections.display_ranges(cx),
 2055            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2056        );
 2057
 2058        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2059        assert_eq!(
 2060            editor.selections.display_ranges(cx),
 2061            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2062        );
 2063
 2064        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2065        assert_eq!(
 2066            editor.selections.display_ranges(cx),
 2067            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2068        );
 2069    });
 2070}
 2071
 2072#[gpui::test]
 2073async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2074    init_test(cx, |_| {});
 2075    let mut cx = EditorTestContext::new(cx).await;
 2076
 2077    let line_height = cx.editor(|editor, window, _| {
 2078        editor
 2079            .style()
 2080            .unwrap()
 2081            .text
 2082            .line_height_in_pixels(window.rem_size())
 2083    });
 2084    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2085
 2086    cx.set_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_end_of_paragraph(&MoveToEndOfParagraph, 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_end_of_paragraph(&MoveToEndOfParagraph, 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_end_of_paragraph(&MoveToEndOfParagraph, 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    cx.update_editor(|editor, window, cx| {
 2144        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2145    });
 2146    cx.assert_editor_state(
 2147        &r#"one
 2148        two
 2149
 2150        three
 2151        four
 2152        five
 2153        ˇ
 2154        six"#
 2155            .unindent(),
 2156    );
 2157
 2158    cx.update_editor(|editor, window, cx| {
 2159        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2160    });
 2161    cx.assert_editor_state(
 2162        &r#"one
 2163        two
 2164        ˇ
 2165        three
 2166        four
 2167        five
 2168
 2169        six"#
 2170            .unindent(),
 2171    );
 2172
 2173    cx.update_editor(|editor, window, cx| {
 2174        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2175    });
 2176    cx.assert_editor_state(
 2177        &r#"ˇone
 2178        two
 2179
 2180        three
 2181        four
 2182        five
 2183
 2184        six"#
 2185            .unindent(),
 2186    );
 2187}
 2188
 2189#[gpui::test]
 2190async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2191    init_test(cx, |_| {});
 2192    let mut cx = EditorTestContext::new(cx).await;
 2193    let line_height = cx.editor(|editor, window, _| {
 2194        editor
 2195            .style()
 2196            .unwrap()
 2197            .text
 2198            .line_height_in_pixels(window.rem_size())
 2199    });
 2200    let window = cx.window;
 2201    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2202
 2203    cx.set_state(
 2204        r#"ˇone
 2205        two
 2206        three
 2207        four
 2208        five
 2209        six
 2210        seven
 2211        eight
 2212        nine
 2213        ten
 2214        "#,
 2215    );
 2216
 2217    cx.update_editor(|editor, window, cx| {
 2218        assert_eq!(
 2219            editor.snapshot(window, cx).scroll_position(),
 2220            gpui::Point::new(0., 0.)
 2221        );
 2222        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2223        assert_eq!(
 2224            editor.snapshot(window, cx).scroll_position(),
 2225            gpui::Point::new(0., 3.)
 2226        );
 2227        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2228        assert_eq!(
 2229            editor.snapshot(window, cx).scroll_position(),
 2230            gpui::Point::new(0., 6.)
 2231        );
 2232        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2233        assert_eq!(
 2234            editor.snapshot(window, cx).scroll_position(),
 2235            gpui::Point::new(0., 3.)
 2236        );
 2237
 2238        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2239        assert_eq!(
 2240            editor.snapshot(window, cx).scroll_position(),
 2241            gpui::Point::new(0., 1.)
 2242        );
 2243        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2244        assert_eq!(
 2245            editor.snapshot(window, cx).scroll_position(),
 2246            gpui::Point::new(0., 3.)
 2247        );
 2248    });
 2249}
 2250
 2251#[gpui::test]
 2252async fn test_autoscroll(cx: &mut TestAppContext) {
 2253    init_test(cx, |_| {});
 2254    let mut cx = EditorTestContext::new(cx).await;
 2255
 2256    let line_height = cx.update_editor(|editor, window, cx| {
 2257        editor.set_vertical_scroll_margin(2, cx);
 2258        editor
 2259            .style()
 2260            .unwrap()
 2261            .text
 2262            .line_height_in_pixels(window.rem_size())
 2263    });
 2264    let window = cx.window;
 2265    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2266
 2267    cx.set_state(
 2268        r#"ˇone
 2269            two
 2270            three
 2271            four
 2272            five
 2273            six
 2274            seven
 2275            eight
 2276            nine
 2277            ten
 2278        "#,
 2279    );
 2280    cx.update_editor(|editor, window, cx| {
 2281        assert_eq!(
 2282            editor.snapshot(window, cx).scroll_position(),
 2283            gpui::Point::new(0., 0.0)
 2284        );
 2285    });
 2286
 2287    // Add a cursor below the visible area. Since both cursors cannot fit
 2288    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2289    // allows the vertical scroll margin below that cursor.
 2290    cx.update_editor(|editor, window, cx| {
 2291        editor.change_selections(Default::default(), window, cx, |selections| {
 2292            selections.select_ranges([
 2293                Point::new(0, 0)..Point::new(0, 0),
 2294                Point::new(6, 0)..Point::new(6, 0),
 2295            ]);
 2296        })
 2297    });
 2298    cx.update_editor(|editor, window, cx| {
 2299        assert_eq!(
 2300            editor.snapshot(window, cx).scroll_position(),
 2301            gpui::Point::new(0., 3.0)
 2302        );
 2303    });
 2304
 2305    // Move down. The editor cursor scrolls down to track the newest cursor.
 2306    cx.update_editor(|editor, window, cx| {
 2307        editor.move_down(&Default::default(), window, cx);
 2308    });
 2309    cx.update_editor(|editor, window, cx| {
 2310        assert_eq!(
 2311            editor.snapshot(window, cx).scroll_position(),
 2312            gpui::Point::new(0., 4.0)
 2313        );
 2314    });
 2315
 2316    // Add a cursor above the visible area. Since both cursors fit on screen,
 2317    // the editor scrolls to show both.
 2318    cx.update_editor(|editor, window, cx| {
 2319        editor.change_selections(Default::default(), window, cx, |selections| {
 2320            selections.select_ranges([
 2321                Point::new(1, 0)..Point::new(1, 0),
 2322                Point::new(6, 0)..Point::new(6, 0),
 2323            ]);
 2324        })
 2325    });
 2326    cx.update_editor(|editor, window, cx| {
 2327        assert_eq!(
 2328            editor.snapshot(window, cx).scroll_position(),
 2329            gpui::Point::new(0., 1.0)
 2330        );
 2331    });
 2332}
 2333
 2334#[gpui::test]
 2335async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2336    init_test(cx, |_| {});
 2337    let mut cx = EditorTestContext::new(cx).await;
 2338
 2339    let line_height = cx.editor(|editor, window, _cx| {
 2340        editor
 2341            .style()
 2342            .unwrap()
 2343            .text
 2344            .line_height_in_pixels(window.rem_size())
 2345    });
 2346    let window = cx.window;
 2347    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2348    cx.set_state(
 2349        &r#"
 2350        ˇone
 2351        two
 2352        threeˇ
 2353        four
 2354        five
 2355        six
 2356        seven
 2357        eight
 2358        nine
 2359        ten
 2360        "#
 2361        .unindent(),
 2362    );
 2363
 2364    cx.update_editor(|editor, window, cx| {
 2365        editor.move_page_down(&MovePageDown::default(), window, cx)
 2366    });
 2367    cx.assert_editor_state(
 2368        &r#"
 2369        one
 2370        two
 2371        three
 2372        ˇfour
 2373        five
 2374        sixˇ
 2375        seven
 2376        eight
 2377        nine
 2378        ten
 2379        "#
 2380        .unindent(),
 2381    );
 2382
 2383    cx.update_editor(|editor, window, cx| {
 2384        editor.move_page_down(&MovePageDown::default(), window, cx)
 2385    });
 2386    cx.assert_editor_state(
 2387        &r#"
 2388        one
 2389        two
 2390        three
 2391        four
 2392        five
 2393        six
 2394        ˇseven
 2395        eight
 2396        nineˇ
 2397        ten
 2398        "#
 2399        .unindent(),
 2400    );
 2401
 2402    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2403    cx.assert_editor_state(
 2404        &r#"
 2405        one
 2406        two
 2407        three
 2408        ˇfour
 2409        five
 2410        sixˇ
 2411        seven
 2412        eight
 2413        nine
 2414        ten
 2415        "#
 2416        .unindent(),
 2417    );
 2418
 2419    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2420    cx.assert_editor_state(
 2421        &r#"
 2422        ˇone
 2423        two
 2424        threeˇ
 2425        four
 2426        five
 2427        six
 2428        seven
 2429        eight
 2430        nine
 2431        ten
 2432        "#
 2433        .unindent(),
 2434    );
 2435
 2436    // Test select collapsing
 2437    cx.update_editor(|editor, window, cx| {
 2438        editor.move_page_down(&MovePageDown::default(), window, cx);
 2439        editor.move_page_down(&MovePageDown::default(), window, cx);
 2440        editor.move_page_down(&MovePageDown::default(), window, cx);
 2441    });
 2442    cx.assert_editor_state(
 2443        &r#"
 2444        one
 2445        two
 2446        three
 2447        four
 2448        five
 2449        six
 2450        seven
 2451        eight
 2452        nine
 2453        ˇten
 2454        ˇ"#
 2455        .unindent(),
 2456    );
 2457}
 2458
 2459#[gpui::test]
 2460async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2461    init_test(cx, |_| {});
 2462    let mut cx = EditorTestContext::new(cx).await;
 2463    cx.set_state("one «two threeˇ» four");
 2464    cx.update_editor(|editor, window, cx| {
 2465        editor.delete_to_beginning_of_line(
 2466            &DeleteToBeginningOfLine {
 2467                stop_at_indent: false,
 2468            },
 2469            window,
 2470            cx,
 2471        );
 2472        assert_eq!(editor.text(cx), " four");
 2473    });
 2474}
 2475
 2476#[gpui::test]
 2477fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2478    init_test(cx, |_| {});
 2479
 2480    let editor = cx.add_window(|window, cx| {
 2481        let buffer = MultiBuffer::build_simple("one two three four", cx);
 2482        build_editor(buffer.clone(), window, cx)
 2483    });
 2484
 2485    _ = editor.update(cx, |editor, window, cx| {
 2486        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2487            s.select_display_ranges([
 2488                // an empty selection - the preceding word fragment is deleted
 2489                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2490                // characters selected - they are deleted
 2491                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
 2492            ])
 2493        });
 2494        editor.delete_to_previous_word_start(
 2495            &DeleteToPreviousWordStart {
 2496                ignore_newlines: false,
 2497            },
 2498            window,
 2499            cx,
 2500        );
 2501        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e two te four");
 2502    });
 2503
 2504    _ = editor.update(cx, |editor, window, cx| {
 2505        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2506            s.select_display_ranges([
 2507                // an empty selection - the following word fragment is deleted
 2508                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 2509                // characters selected - they are deleted
 2510                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
 2511            ])
 2512        });
 2513        editor.delete_to_next_word_end(
 2514            &DeleteToNextWordEnd {
 2515                ignore_newlines: false,
 2516            },
 2517            window,
 2518            cx,
 2519        );
 2520        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e t te our");
 2521    });
 2522}
 2523
 2524#[gpui::test]
 2525fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2526    init_test(cx, |_| {});
 2527
 2528    let editor = cx.add_window(|window, cx| {
 2529        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 2530        build_editor(buffer.clone(), window, cx)
 2531    });
 2532    let del_to_prev_word_start = DeleteToPreviousWordStart {
 2533        ignore_newlines: false,
 2534    };
 2535    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 2536        ignore_newlines: true,
 2537    };
 2538
 2539    _ = editor.update(cx, |editor, window, cx| {
 2540        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2541            s.select_display_ranges([
 2542                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 2543            ])
 2544        });
 2545        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2546        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 2547        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2548        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 2549        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2550        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 2551        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2552        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 2553        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2554        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 2555        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2556        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2557    });
 2558}
 2559
 2560#[gpui::test]
 2561fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 2562    init_test(cx, |_| {});
 2563
 2564    let editor = cx.add_window(|window, cx| {
 2565        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 2566        build_editor(buffer.clone(), window, cx)
 2567    });
 2568    let del_to_next_word_end = DeleteToNextWordEnd {
 2569        ignore_newlines: false,
 2570    };
 2571    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 2572        ignore_newlines: true,
 2573    };
 2574
 2575    _ = editor.update(cx, |editor, window, cx| {
 2576        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2577            s.select_display_ranges([
 2578                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 2579            ])
 2580        });
 2581        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2582        assert_eq!(
 2583            editor.buffer.read(cx).read(cx).text(),
 2584            "one\n   two\nthree\n   four"
 2585        );
 2586        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2587        assert_eq!(
 2588            editor.buffer.read(cx).read(cx).text(),
 2589            "\n   two\nthree\n   four"
 2590        );
 2591        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2592        assert_eq!(
 2593            editor.buffer.read(cx).read(cx).text(),
 2594            "two\nthree\n   four"
 2595        );
 2596        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2597        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 2598        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2599        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 2600        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2601        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2602    });
 2603}
 2604
 2605#[gpui::test]
 2606fn test_newline(cx: &mut TestAppContext) {
 2607    init_test(cx, |_| {});
 2608
 2609    let editor = cx.add_window(|window, cx| {
 2610        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 2611        build_editor(buffer.clone(), window, cx)
 2612    });
 2613
 2614    _ = editor.update(cx, |editor, window, cx| {
 2615        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2616            s.select_display_ranges([
 2617                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2618                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2619                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 2620            ])
 2621        });
 2622
 2623        editor.newline(&Newline, window, cx);
 2624        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 2625    });
 2626}
 2627
 2628#[gpui::test]
 2629fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 2630    init_test(cx, |_| {});
 2631
 2632    let editor = cx.add_window(|window, cx| {
 2633        let buffer = MultiBuffer::build_simple(
 2634            "
 2635                a
 2636                b(
 2637                    X
 2638                )
 2639                c(
 2640                    X
 2641                )
 2642            "
 2643            .unindent()
 2644            .as_str(),
 2645            cx,
 2646        );
 2647        let mut editor = build_editor(buffer.clone(), window, cx);
 2648        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2649            s.select_ranges([
 2650                Point::new(2, 4)..Point::new(2, 5),
 2651                Point::new(5, 4)..Point::new(5, 5),
 2652            ])
 2653        });
 2654        editor
 2655    });
 2656
 2657    _ = editor.update(cx, |editor, window, cx| {
 2658        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 2659        editor.buffer.update(cx, |buffer, cx| {
 2660            buffer.edit(
 2661                [
 2662                    (Point::new(1, 2)..Point::new(3, 0), ""),
 2663                    (Point::new(4, 2)..Point::new(6, 0), ""),
 2664                ],
 2665                None,
 2666                cx,
 2667            );
 2668            assert_eq!(
 2669                buffer.read(cx).text(),
 2670                "
 2671                    a
 2672                    b()
 2673                    c()
 2674                "
 2675                .unindent()
 2676            );
 2677        });
 2678        assert_eq!(
 2679            editor.selections.ranges(cx),
 2680            &[
 2681                Point::new(1, 2)..Point::new(1, 2),
 2682                Point::new(2, 2)..Point::new(2, 2),
 2683            ],
 2684        );
 2685
 2686        editor.newline(&Newline, window, cx);
 2687        assert_eq!(
 2688            editor.text(cx),
 2689            "
 2690                a
 2691                b(
 2692                )
 2693                c(
 2694                )
 2695            "
 2696            .unindent()
 2697        );
 2698
 2699        // The selections are moved after the inserted newlines
 2700        assert_eq!(
 2701            editor.selections.ranges(cx),
 2702            &[
 2703                Point::new(2, 0)..Point::new(2, 0),
 2704                Point::new(4, 0)..Point::new(4, 0),
 2705            ],
 2706        );
 2707    });
 2708}
 2709
 2710#[gpui::test]
 2711async fn test_newline_above(cx: &mut TestAppContext) {
 2712    init_test(cx, |settings| {
 2713        settings.defaults.tab_size = NonZeroU32::new(4)
 2714    });
 2715
 2716    let language = Arc::new(
 2717        Language::new(
 2718            LanguageConfig::default(),
 2719            Some(tree_sitter_rust::LANGUAGE.into()),
 2720        )
 2721        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2722        .unwrap(),
 2723    );
 2724
 2725    let mut cx = EditorTestContext::new(cx).await;
 2726    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2727    cx.set_state(indoc! {"
 2728        const a: ˇA = (
 2729 2730                «const_functionˇ»(ˇ),
 2731                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2732 2733        ˇ);ˇ
 2734    "});
 2735
 2736    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 2737    cx.assert_editor_state(indoc! {"
 2738        ˇ
 2739        const a: A = (
 2740            ˇ
 2741            (
 2742                ˇ
 2743                ˇ
 2744                const_function(),
 2745                ˇ
 2746                ˇ
 2747                ˇ
 2748                ˇ
 2749                something_else,
 2750                ˇ
 2751            )
 2752            ˇ
 2753            ˇ
 2754        );
 2755    "});
 2756}
 2757
 2758#[gpui::test]
 2759async fn test_newline_below(cx: &mut TestAppContext) {
 2760    init_test(cx, |settings| {
 2761        settings.defaults.tab_size = NonZeroU32::new(4)
 2762    });
 2763
 2764    let language = Arc::new(
 2765        Language::new(
 2766            LanguageConfig::default(),
 2767            Some(tree_sitter_rust::LANGUAGE.into()),
 2768        )
 2769        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2770        .unwrap(),
 2771    );
 2772
 2773    let mut cx = EditorTestContext::new(cx).await;
 2774    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2775    cx.set_state(indoc! {"
 2776        const a: ˇA = (
 2777 2778                «const_functionˇ»(ˇ),
 2779                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2780 2781        ˇ);ˇ
 2782    "});
 2783
 2784    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 2785    cx.assert_editor_state(indoc! {"
 2786        const a: A = (
 2787            ˇ
 2788            (
 2789                ˇ
 2790                const_function(),
 2791                ˇ
 2792                ˇ
 2793                something_else,
 2794                ˇ
 2795                ˇ
 2796                ˇ
 2797                ˇ
 2798            )
 2799            ˇ
 2800        );
 2801        ˇ
 2802        ˇ
 2803    "});
 2804}
 2805
 2806#[gpui::test]
 2807async fn test_newline_comments(cx: &mut TestAppContext) {
 2808    init_test(cx, |settings| {
 2809        settings.defaults.tab_size = NonZeroU32::new(4)
 2810    });
 2811
 2812    let language = Arc::new(Language::new(
 2813        LanguageConfig {
 2814            line_comments: vec!["// ".into()],
 2815            ..LanguageConfig::default()
 2816        },
 2817        None,
 2818    ));
 2819    {
 2820        let mut cx = EditorTestContext::new(cx).await;
 2821        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2822        cx.set_state(indoc! {"
 2823        // Fooˇ
 2824    "});
 2825
 2826        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2827        cx.assert_editor_state(indoc! {"
 2828        // Foo
 2829        // ˇ
 2830    "});
 2831        // Ensure that we add comment prefix when existing line contains space
 2832        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2833        cx.assert_editor_state(
 2834            indoc! {"
 2835        // Foo
 2836        //s
 2837        // ˇ
 2838    "}
 2839            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2840            .as_str(),
 2841        );
 2842        // Ensure that we add comment prefix when existing line does not contain space
 2843        cx.set_state(indoc! {"
 2844        // Foo
 2845        //ˇ
 2846    "});
 2847        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2848        cx.assert_editor_state(indoc! {"
 2849        // Foo
 2850        //
 2851        // ˇ
 2852    "});
 2853        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 2854        cx.set_state(indoc! {"
 2855        ˇ// Foo
 2856    "});
 2857        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2858        cx.assert_editor_state(indoc! {"
 2859
 2860        ˇ// Foo
 2861    "});
 2862    }
 2863    // Ensure that comment continuations can be disabled.
 2864    update_test_language_settings(cx, |settings| {
 2865        settings.defaults.extend_comment_on_newline = Some(false);
 2866    });
 2867    let mut cx = EditorTestContext::new(cx).await;
 2868    cx.set_state(indoc! {"
 2869        // Fooˇ
 2870    "});
 2871    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2872    cx.assert_editor_state(indoc! {"
 2873        // Foo
 2874        ˇ
 2875    "});
 2876}
 2877
 2878#[gpui::test]
 2879async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 2880    init_test(cx, |settings| {
 2881        settings.defaults.tab_size = NonZeroU32::new(4)
 2882    });
 2883
 2884    let language = Arc::new(Language::new(
 2885        LanguageConfig {
 2886            line_comments: vec!["// ".into(), "/// ".into()],
 2887            ..LanguageConfig::default()
 2888        },
 2889        None,
 2890    ));
 2891    {
 2892        let mut cx = EditorTestContext::new(cx).await;
 2893        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2894        cx.set_state(indoc! {"
 2895        //ˇ
 2896    "});
 2897        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2898        cx.assert_editor_state(indoc! {"
 2899        //
 2900        // ˇ
 2901    "});
 2902
 2903        cx.set_state(indoc! {"
 2904        ///ˇ
 2905    "});
 2906        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2907        cx.assert_editor_state(indoc! {"
 2908        ///
 2909        /// ˇ
 2910    "});
 2911    }
 2912}
 2913
 2914#[gpui::test]
 2915async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 2916    init_test(cx, |settings| {
 2917        settings.defaults.tab_size = NonZeroU32::new(4)
 2918    });
 2919
 2920    let language = Arc::new(
 2921        Language::new(
 2922            LanguageConfig {
 2923                documentation_comment: Some(language::BlockCommentConfig {
 2924                    start: "/**".into(),
 2925                    end: "*/".into(),
 2926                    prefix: "* ".into(),
 2927                    tab_size: 1,
 2928                }),
 2929
 2930                ..LanguageConfig::default()
 2931            },
 2932            Some(tree_sitter_rust::LANGUAGE.into()),
 2933        )
 2934        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 2935        .unwrap(),
 2936    );
 2937
 2938    {
 2939        let mut cx = EditorTestContext::new(cx).await;
 2940        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2941        cx.set_state(indoc! {"
 2942        /**ˇ
 2943    "});
 2944
 2945        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2946        cx.assert_editor_state(indoc! {"
 2947        /**
 2948         * ˇ
 2949    "});
 2950        // Ensure that if cursor is before the comment start,
 2951        // we do not actually insert a comment prefix.
 2952        cx.set_state(indoc! {"
 2953        ˇ/**
 2954    "});
 2955        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2956        cx.assert_editor_state(indoc! {"
 2957
 2958        ˇ/**
 2959    "});
 2960        // Ensure that if cursor is between it doesn't add comment prefix.
 2961        cx.set_state(indoc! {"
 2962        /*ˇ*
 2963    "});
 2964        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2965        cx.assert_editor_state(indoc! {"
 2966        /*
 2967        ˇ*
 2968    "});
 2969        // Ensure that if suffix exists on same line after cursor it adds new line.
 2970        cx.set_state(indoc! {"
 2971        /**ˇ*/
 2972    "});
 2973        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2974        cx.assert_editor_state(indoc! {"
 2975        /**
 2976         * ˇ
 2977         */
 2978    "});
 2979        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2980        cx.set_state(indoc! {"
 2981        /**ˇ */
 2982    "});
 2983        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2984        cx.assert_editor_state(indoc! {"
 2985        /**
 2986         * ˇ
 2987         */
 2988    "});
 2989        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2990        cx.set_state(indoc! {"
 2991        /** ˇ*/
 2992    "});
 2993        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2994        cx.assert_editor_state(
 2995            indoc! {"
 2996        /**s
 2997         * ˇ
 2998         */
 2999    "}
 3000            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3001            .as_str(),
 3002        );
 3003        // Ensure that delimiter space is preserved when newline on already
 3004        // spaced delimiter.
 3005        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3006        cx.assert_editor_state(
 3007            indoc! {"
 3008        /**s
 3009         *s
 3010         * ˇ
 3011         */
 3012    "}
 3013            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3014            .as_str(),
 3015        );
 3016        // Ensure that delimiter space is preserved when space is not
 3017        // on existing delimiter.
 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        // Ensure that if suffix exists on same line after cursor it
 3031        // doesn't add extra new line if prefix is not on same line.
 3032        cx.set_state(indoc! {"
 3033        /**
 3034        ˇ*/
 3035    "});
 3036        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3037        cx.assert_editor_state(indoc! {"
 3038        /**
 3039
 3040        ˇ*/
 3041    "});
 3042        // Ensure that it detects suffix after existing prefix.
 3043        cx.set_state(indoc! {"
 3044        /**ˇ/
 3045    "});
 3046        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3047        cx.assert_editor_state(indoc! {"
 3048        /**
 3049        ˇ/
 3050    "});
 3051        // Ensure that if suffix exists on same line before
 3052        // cursor it does not add comment prefix.
 3053        cx.set_state(indoc! {"
 3054        /** */ˇ
 3055    "});
 3056        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3057        cx.assert_editor_state(indoc! {"
 3058        /** */
 3059        ˇ
 3060    "});
 3061        // Ensure that if suffix exists on same line before
 3062        // cursor it does not add comment prefix.
 3063        cx.set_state(indoc! {"
 3064        /**
 3065         *
 3066         */ˇ
 3067    "});
 3068        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3069        cx.assert_editor_state(indoc! {"
 3070        /**
 3071         *
 3072         */
 3073         ˇ
 3074    "});
 3075
 3076        // Ensure that inline comment followed by code
 3077        // doesn't add comment prefix on newline
 3078        cx.set_state(indoc! {"
 3079        /** */ textˇ
 3080    "});
 3081        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3082        cx.assert_editor_state(indoc! {"
 3083        /** */ text
 3084        ˇ
 3085    "});
 3086
 3087        // Ensure that text after comment end tag
 3088        // doesn't add comment prefix on newline
 3089        cx.set_state(indoc! {"
 3090        /**
 3091         *
 3092         */ˇtext
 3093    "});
 3094        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3095        cx.assert_editor_state(indoc! {"
 3096        /**
 3097         *
 3098         */
 3099         ˇtext
 3100    "});
 3101
 3102        // Ensure if not comment block it doesn't
 3103        // add comment prefix on newline
 3104        cx.set_state(indoc! {"
 3105        * textˇ
 3106    "});
 3107        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3108        cx.assert_editor_state(indoc! {"
 3109        * text
 3110        ˇ
 3111    "});
 3112    }
 3113    // Ensure that comment continuations can be disabled.
 3114    update_test_language_settings(cx, |settings| {
 3115        settings.defaults.extend_comment_on_newline = Some(false);
 3116    });
 3117    let mut cx = EditorTestContext::new(cx).await;
 3118    cx.set_state(indoc! {"
 3119        /**ˇ
 3120    "});
 3121    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3122    cx.assert_editor_state(indoc! {"
 3123        /**
 3124        ˇ
 3125    "});
 3126}
 3127
 3128#[gpui::test]
 3129async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
 3130    init_test(cx, |settings| {
 3131        settings.defaults.tab_size = NonZeroU32::new(4)
 3132    });
 3133
 3134    let lua_language = Arc::new(Language::new(
 3135        LanguageConfig {
 3136            line_comments: vec!["--".into()],
 3137            block_comment: Some(language::BlockCommentConfig {
 3138                start: "--[[".into(),
 3139                prefix: "".into(),
 3140                end: "]]".into(),
 3141                tab_size: 0,
 3142            }),
 3143            ..LanguageConfig::default()
 3144        },
 3145        None,
 3146    ));
 3147
 3148    let mut cx = EditorTestContext::new(cx).await;
 3149    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 3150
 3151    // Line with line comment should extend
 3152    cx.set_state(indoc! {"
 3153        --ˇ
 3154    "});
 3155    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3156    cx.assert_editor_state(indoc! {"
 3157        --
 3158        --ˇ
 3159    "});
 3160
 3161    // Line with block comment that matches line comment should not extend
 3162    cx.set_state(indoc! {"
 3163        --[[ˇ
 3164    "});
 3165    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3166    cx.assert_editor_state(indoc! {"
 3167        --[[
 3168        ˇ
 3169    "});
 3170}
 3171
 3172#[gpui::test]
 3173fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3174    init_test(cx, |_| {});
 3175
 3176    let editor = cx.add_window(|window, cx| {
 3177        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3178        let mut editor = build_editor(buffer.clone(), window, cx);
 3179        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3180            s.select_ranges([3..4, 11..12, 19..20])
 3181        });
 3182        editor
 3183    });
 3184
 3185    _ = editor.update(cx, |editor, window, cx| {
 3186        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3187        editor.buffer.update(cx, |buffer, cx| {
 3188            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 3189            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3190        });
 3191        assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
 3192
 3193        editor.insert("Z", window, cx);
 3194        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3195
 3196        // The selections are moved after the inserted characters
 3197        assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
 3198    });
 3199}
 3200
 3201#[gpui::test]
 3202async fn test_tab(cx: &mut TestAppContext) {
 3203    init_test(cx, |settings| {
 3204        settings.defaults.tab_size = NonZeroU32::new(3)
 3205    });
 3206
 3207    let mut cx = EditorTestContext::new(cx).await;
 3208    cx.set_state(indoc! {"
 3209        ˇabˇc
 3210        ˇ🏀ˇ🏀ˇefg
 3211 3212    "});
 3213    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3214    cx.assert_editor_state(indoc! {"
 3215           ˇab ˇc
 3216           ˇ🏀  ˇ🏀  ˇefg
 3217        d  ˇ
 3218    "});
 3219
 3220    cx.set_state(indoc! {"
 3221        a
 3222        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3223    "});
 3224    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3225    cx.assert_editor_state(indoc! {"
 3226        a
 3227           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3228    "});
 3229}
 3230
 3231#[gpui::test]
 3232async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3233    init_test(cx, |_| {});
 3234
 3235    let mut cx = EditorTestContext::new(cx).await;
 3236    let language = Arc::new(
 3237        Language::new(
 3238            LanguageConfig::default(),
 3239            Some(tree_sitter_rust::LANGUAGE.into()),
 3240        )
 3241        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3242        .unwrap(),
 3243    );
 3244    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3245
 3246    // test when all cursors are not at suggested indent
 3247    // then simply move to their suggested indent location
 3248    cx.set_state(indoc! {"
 3249        const a: B = (
 3250            c(
 3251        ˇ
 3252        ˇ    )
 3253        );
 3254    "});
 3255    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3256    cx.assert_editor_state(indoc! {"
 3257        const a: B = (
 3258            c(
 3259                ˇ
 3260            ˇ)
 3261        );
 3262    "});
 3263
 3264    // test cursor already at suggested indent not moving when
 3265    // other cursors are yet to reach their suggested indents
 3266    cx.set_state(indoc! {"
 3267        ˇ
 3268        const a: B = (
 3269            c(
 3270                d(
 3271        ˇ
 3272                )
 3273        ˇ
 3274        ˇ    )
 3275        );
 3276    "});
 3277    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3278    cx.assert_editor_state(indoc! {"
 3279        ˇ
 3280        const a: B = (
 3281            c(
 3282                d(
 3283                    ˇ
 3284                )
 3285                ˇ
 3286            ˇ)
 3287        );
 3288    "});
 3289    // test when all cursors are at suggested indent then tab is inserted
 3290    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3291    cx.assert_editor_state(indoc! {"
 3292            ˇ
 3293        const a: B = (
 3294            c(
 3295                d(
 3296                        ˇ
 3297                )
 3298                    ˇ
 3299                ˇ)
 3300        );
 3301    "});
 3302
 3303    // test when current indent is less than suggested indent,
 3304    // we adjust line to match suggested indent and move cursor to it
 3305    //
 3306    // when no other cursor is at word boundary, all of them should move
 3307    cx.set_state(indoc! {"
 3308        const a: B = (
 3309            c(
 3310                d(
 3311        ˇ
 3312        ˇ   )
 3313        ˇ   )
 3314        );
 3315    "});
 3316    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3317    cx.assert_editor_state(indoc! {"
 3318        const a: B = (
 3319            c(
 3320                d(
 3321                    ˇ
 3322                ˇ)
 3323            ˇ)
 3324        );
 3325    "});
 3326
 3327    // test when current indent is less than suggested indent,
 3328    // we adjust line to match suggested indent and move cursor to it
 3329    //
 3330    // when some other cursor is at word boundary, it should not move
 3331    cx.set_state(indoc! {"
 3332        const a: B = (
 3333            c(
 3334                d(
 3335        ˇ
 3336        ˇ   )
 3337           ˇ)
 3338        );
 3339    "});
 3340    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3341    cx.assert_editor_state(indoc! {"
 3342        const a: B = (
 3343            c(
 3344                d(
 3345                    ˇ
 3346                ˇ)
 3347            ˇ)
 3348        );
 3349    "});
 3350
 3351    // test when current indent is more than suggested indent,
 3352    // we just move cursor to current indent instead of suggested indent
 3353    //
 3354    // when no other cursor is at word boundary, all of them should move
 3355    cx.set_state(indoc! {"
 3356        const a: B = (
 3357            c(
 3358                d(
 3359        ˇ
 3360        ˇ                )
 3361        ˇ   )
 3362        );
 3363    "});
 3364    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3365    cx.assert_editor_state(indoc! {"
 3366        const a: B = (
 3367            c(
 3368                d(
 3369                    ˇ
 3370                        ˇ)
 3371            ˇ)
 3372        );
 3373    "});
 3374    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3375    cx.assert_editor_state(indoc! {"
 3376        const a: B = (
 3377            c(
 3378                d(
 3379                        ˇ
 3380                            ˇ)
 3381                ˇ)
 3382        );
 3383    "});
 3384
 3385    // test when current indent is more than suggested indent,
 3386    // we just move cursor to current indent instead of suggested indent
 3387    //
 3388    // when some other cursor is at word boundary, it doesn't move
 3389    cx.set_state(indoc! {"
 3390        const a: B = (
 3391            c(
 3392                d(
 3393        ˇ
 3394        ˇ                )
 3395            ˇ)
 3396        );
 3397    "});
 3398    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3399    cx.assert_editor_state(indoc! {"
 3400        const a: B = (
 3401            c(
 3402                d(
 3403                    ˇ
 3404                        ˇ)
 3405            ˇ)
 3406        );
 3407    "});
 3408
 3409    // handle auto-indent when there are multiple cursors on the same line
 3410    cx.set_state(indoc! {"
 3411        const a: B = (
 3412            c(
 3413        ˇ    ˇ
 3414        ˇ    )
 3415        );
 3416    "});
 3417    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3418    cx.assert_editor_state(indoc! {"
 3419        const a: B = (
 3420            c(
 3421                ˇ
 3422            ˇ)
 3423        );
 3424    "});
 3425}
 3426
 3427#[gpui::test]
 3428async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 3429    init_test(cx, |settings| {
 3430        settings.defaults.tab_size = NonZeroU32::new(3)
 3431    });
 3432
 3433    let mut cx = EditorTestContext::new(cx).await;
 3434    cx.set_state(indoc! {"
 3435         ˇ
 3436        \t ˇ
 3437        \t  ˇ
 3438        \t   ˇ
 3439         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 3440    "});
 3441
 3442    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3443    cx.assert_editor_state(indoc! {"
 3444           ˇ
 3445        \t   ˇ
 3446        \t   ˇ
 3447        \t      ˇ
 3448         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 3449    "});
 3450}
 3451
 3452#[gpui::test]
 3453async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 3454    init_test(cx, |settings| {
 3455        settings.defaults.tab_size = NonZeroU32::new(4)
 3456    });
 3457
 3458    let language = Arc::new(
 3459        Language::new(
 3460            LanguageConfig::default(),
 3461            Some(tree_sitter_rust::LANGUAGE.into()),
 3462        )
 3463        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 3464        .unwrap(),
 3465    );
 3466
 3467    let mut cx = EditorTestContext::new(cx).await;
 3468    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3469    cx.set_state(indoc! {"
 3470        fn a() {
 3471            if b {
 3472        \t ˇc
 3473            }
 3474        }
 3475    "});
 3476
 3477    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3478    cx.assert_editor_state(indoc! {"
 3479        fn a() {
 3480            if b {
 3481                ˇc
 3482            }
 3483        }
 3484    "});
 3485}
 3486
 3487#[gpui::test]
 3488async fn test_indent_outdent(cx: &mut TestAppContext) {
 3489    init_test(cx, |settings| {
 3490        settings.defaults.tab_size = NonZeroU32::new(4);
 3491    });
 3492
 3493    let mut cx = EditorTestContext::new(cx).await;
 3494
 3495    cx.set_state(indoc! {"
 3496          «oneˇ» «twoˇ»
 3497        three
 3498         four
 3499    "});
 3500    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3501    cx.assert_editor_state(indoc! {"
 3502            «oneˇ» «twoˇ»
 3503        three
 3504         four
 3505    "});
 3506
 3507    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3508    cx.assert_editor_state(indoc! {"
 3509        «oneˇ» «twoˇ»
 3510        three
 3511         four
 3512    "});
 3513
 3514    // select across line ending
 3515    cx.set_state(indoc! {"
 3516        one two
 3517        t«hree
 3518        ˇ» four
 3519    "});
 3520    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3521    cx.assert_editor_state(indoc! {"
 3522        one two
 3523            t«hree
 3524        ˇ» four
 3525    "});
 3526
 3527    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3528    cx.assert_editor_state(indoc! {"
 3529        one two
 3530        t«hree
 3531        ˇ» four
 3532    "});
 3533
 3534    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3535    cx.set_state(indoc! {"
 3536        one two
 3537        ˇthree
 3538            four
 3539    "});
 3540    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3541    cx.assert_editor_state(indoc! {"
 3542        one two
 3543            ˇthree
 3544            four
 3545    "});
 3546
 3547    cx.set_state(indoc! {"
 3548        one two
 3549        ˇ    three
 3550            four
 3551    "});
 3552    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3553    cx.assert_editor_state(indoc! {"
 3554        one two
 3555        ˇthree
 3556            four
 3557    "});
 3558}
 3559
 3560#[gpui::test]
 3561async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3562    // This is a regression test for issue #33761
 3563    init_test(cx, |_| {});
 3564
 3565    let mut cx = EditorTestContext::new(cx).await;
 3566    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3567    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3568
 3569    cx.set_state(
 3570        r#"ˇ#     ingress:
 3571ˇ#         api:
 3572ˇ#             enabled: false
 3573ˇ#             pathType: Prefix
 3574ˇ#           console:
 3575ˇ#               enabled: false
 3576ˇ#               pathType: Prefix
 3577"#,
 3578    );
 3579
 3580    // Press tab to indent all lines
 3581    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3582
 3583    cx.assert_editor_state(
 3584        r#"    ˇ#     ingress:
 3585    ˇ#         api:
 3586    ˇ#             enabled: false
 3587    ˇ#             pathType: Prefix
 3588    ˇ#           console:
 3589    ˇ#               enabled: false
 3590    ˇ#               pathType: Prefix
 3591"#,
 3592    );
 3593}
 3594
 3595#[gpui::test]
 3596async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3597    // This is a test to make sure our fix for issue #33761 didn't break anything
 3598    init_test(cx, |_| {});
 3599
 3600    let mut cx = EditorTestContext::new(cx).await;
 3601    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3602    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3603
 3604    cx.set_state(
 3605        r#"ˇingress:
 3606ˇ  api:
 3607ˇ    enabled: false
 3608ˇ    pathType: Prefix
 3609"#,
 3610    );
 3611
 3612    // Press tab to indent all lines
 3613    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3614
 3615    cx.assert_editor_state(
 3616        r#"ˇingress:
 3617    ˇapi:
 3618        ˇenabled: false
 3619        ˇpathType: Prefix
 3620"#,
 3621    );
 3622}
 3623
 3624#[gpui::test]
 3625async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 3626    init_test(cx, |settings| {
 3627        settings.defaults.hard_tabs = Some(true);
 3628    });
 3629
 3630    let mut cx = EditorTestContext::new(cx).await;
 3631
 3632    // select two ranges on one line
 3633    cx.set_state(indoc! {"
 3634        «oneˇ» «twoˇ»
 3635        three
 3636        four
 3637    "});
 3638    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3639    cx.assert_editor_state(indoc! {"
 3640        \t«oneˇ» «twoˇ»
 3641        three
 3642        four
 3643    "});
 3644    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3645    cx.assert_editor_state(indoc! {"
 3646        \t\t«oneˇ» «twoˇ»
 3647        three
 3648        four
 3649    "});
 3650    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3651    cx.assert_editor_state(indoc! {"
 3652        \t«oneˇ» «twoˇ»
 3653        three
 3654        four
 3655    "});
 3656    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3657    cx.assert_editor_state(indoc! {"
 3658        «oneˇ» «twoˇ»
 3659        three
 3660        four
 3661    "});
 3662
 3663    // select across a line ending
 3664    cx.set_state(indoc! {"
 3665        one two
 3666        t«hree
 3667        ˇ»four
 3668    "});
 3669    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3670    cx.assert_editor_state(indoc! {"
 3671        one two
 3672        \tt«hree
 3673        ˇ»four
 3674    "});
 3675    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3676    cx.assert_editor_state(indoc! {"
 3677        one two
 3678        \t\tt«hree
 3679        ˇ»four
 3680    "});
 3681    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3682    cx.assert_editor_state(indoc! {"
 3683        one two
 3684        \tt«hree
 3685        ˇ»four
 3686    "});
 3687    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3688    cx.assert_editor_state(indoc! {"
 3689        one two
 3690        t«hree
 3691        ˇ»four
 3692    "});
 3693
 3694    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3695    cx.set_state(indoc! {"
 3696        one two
 3697        ˇthree
 3698        four
 3699    "});
 3700    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3701    cx.assert_editor_state(indoc! {"
 3702        one two
 3703        ˇthree
 3704        four
 3705    "});
 3706    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3707    cx.assert_editor_state(indoc! {"
 3708        one two
 3709        \tˇthree
 3710        four
 3711    "});
 3712    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3713    cx.assert_editor_state(indoc! {"
 3714        one two
 3715        ˇthree
 3716        four
 3717    "});
 3718}
 3719
 3720#[gpui::test]
 3721fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 3722    init_test(cx, |settings| {
 3723        settings.languages.0.extend([
 3724            (
 3725                "TOML".into(),
 3726                LanguageSettingsContent {
 3727                    tab_size: NonZeroU32::new(2),
 3728                    ..Default::default()
 3729                },
 3730            ),
 3731            (
 3732                "Rust".into(),
 3733                LanguageSettingsContent {
 3734                    tab_size: NonZeroU32::new(4),
 3735                    ..Default::default()
 3736                },
 3737            ),
 3738        ]);
 3739    });
 3740
 3741    let toml_language = Arc::new(Language::new(
 3742        LanguageConfig {
 3743            name: "TOML".into(),
 3744            ..Default::default()
 3745        },
 3746        None,
 3747    ));
 3748    let rust_language = Arc::new(Language::new(
 3749        LanguageConfig {
 3750            name: "Rust".into(),
 3751            ..Default::default()
 3752        },
 3753        None,
 3754    ));
 3755
 3756    let toml_buffer =
 3757        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 3758    let rust_buffer =
 3759        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 3760    let multibuffer = cx.new(|cx| {
 3761        let mut multibuffer = MultiBuffer::new(ReadWrite);
 3762        multibuffer.push_excerpts(
 3763            toml_buffer.clone(),
 3764            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 3765            cx,
 3766        );
 3767        multibuffer.push_excerpts(
 3768            rust_buffer.clone(),
 3769            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 3770            cx,
 3771        );
 3772        multibuffer
 3773    });
 3774
 3775    cx.add_window(|window, cx| {
 3776        let mut editor = build_editor(multibuffer, window, cx);
 3777
 3778        assert_eq!(
 3779            editor.text(cx),
 3780            indoc! {"
 3781                a = 1
 3782                b = 2
 3783
 3784                const c: usize = 3;
 3785            "}
 3786        );
 3787
 3788        select_ranges(
 3789            &mut editor,
 3790            indoc! {"
 3791                «aˇ» = 1
 3792                b = 2
 3793
 3794                «const c:ˇ» usize = 3;
 3795            "},
 3796            window,
 3797            cx,
 3798        );
 3799
 3800        editor.tab(&Tab, window, cx);
 3801        assert_text_with_selections(
 3802            &mut editor,
 3803            indoc! {"
 3804                  «aˇ» = 1
 3805                b = 2
 3806
 3807                    «const c:ˇ» usize = 3;
 3808            "},
 3809            cx,
 3810        );
 3811        editor.backtab(&Backtab, window, cx);
 3812        assert_text_with_selections(
 3813            &mut editor,
 3814            indoc! {"
 3815                «aˇ» = 1
 3816                b = 2
 3817
 3818                «const c:ˇ» usize = 3;
 3819            "},
 3820            cx,
 3821        );
 3822
 3823        editor
 3824    });
 3825}
 3826
 3827#[gpui::test]
 3828async fn test_backspace(cx: &mut TestAppContext) {
 3829    init_test(cx, |_| {});
 3830
 3831    let mut cx = EditorTestContext::new(cx).await;
 3832
 3833    // Basic backspace
 3834    cx.set_state(indoc! {"
 3835        onˇe two three
 3836        fou«rˇ» five six
 3837        seven «ˇeight nine
 3838        »ten
 3839    "});
 3840    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3841    cx.assert_editor_state(indoc! {"
 3842        oˇe two three
 3843        fouˇ five six
 3844        seven ˇten
 3845    "});
 3846
 3847    // Test backspace inside and around indents
 3848    cx.set_state(indoc! {"
 3849        zero
 3850            ˇone
 3851                ˇtwo
 3852            ˇ ˇ ˇ  three
 3853        ˇ  ˇ  four
 3854    "});
 3855    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3856    cx.assert_editor_state(indoc! {"
 3857        zero
 3858        ˇone
 3859            ˇtwo
 3860        ˇ  threeˇ  four
 3861    "});
 3862}
 3863
 3864#[gpui::test]
 3865async fn test_delete(cx: &mut TestAppContext) {
 3866    init_test(cx, |_| {});
 3867
 3868    let mut cx = EditorTestContext::new(cx).await;
 3869    cx.set_state(indoc! {"
 3870        onˇe two three
 3871        fou«rˇ» five six
 3872        seven «ˇeight nine
 3873        »ten
 3874    "});
 3875    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 3876    cx.assert_editor_state(indoc! {"
 3877        onˇ two three
 3878        fouˇ five six
 3879        seven ˇten
 3880    "});
 3881}
 3882
 3883#[gpui::test]
 3884fn test_delete_line(cx: &mut TestAppContext) {
 3885    init_test(cx, |_| {});
 3886
 3887    let editor = cx.add_window(|window, cx| {
 3888        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3889        build_editor(buffer, window, cx)
 3890    });
 3891    _ = editor.update(cx, |editor, window, cx| {
 3892        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3893            s.select_display_ranges([
 3894                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 3895                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 3896                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 3897            ])
 3898        });
 3899        editor.delete_line(&DeleteLine, window, cx);
 3900        assert_eq!(editor.display_text(cx), "ghi");
 3901        assert_eq!(
 3902            editor.selections.display_ranges(cx),
 3903            vec![
 3904                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 3905                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
 3906            ]
 3907        );
 3908    });
 3909
 3910    let editor = cx.add_window(|window, cx| {
 3911        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3912        build_editor(buffer, window, cx)
 3913    });
 3914    _ = editor.update(cx, |editor, window, cx| {
 3915        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3916            s.select_display_ranges([
 3917                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 3918            ])
 3919        });
 3920        editor.delete_line(&DeleteLine, window, cx);
 3921        assert_eq!(editor.display_text(cx), "ghi\n");
 3922        assert_eq!(
 3923            editor.selections.display_ranges(cx),
 3924            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 3925        );
 3926    });
 3927}
 3928
 3929#[gpui::test]
 3930fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 3931    init_test(cx, |_| {});
 3932
 3933    cx.add_window(|window, cx| {
 3934        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3935        let mut editor = build_editor(buffer.clone(), window, cx);
 3936        let buffer = buffer.read(cx).as_singleton().unwrap();
 3937
 3938        assert_eq!(
 3939            editor.selections.ranges::<Point>(cx),
 3940            &[Point::new(0, 0)..Point::new(0, 0)]
 3941        );
 3942
 3943        // When on single line, replace newline at end by space
 3944        editor.join_lines(&JoinLines, window, cx);
 3945        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3946        assert_eq!(
 3947            editor.selections.ranges::<Point>(cx),
 3948            &[Point::new(0, 3)..Point::new(0, 3)]
 3949        );
 3950
 3951        // When multiple lines are selected, remove newlines that are spanned by the selection
 3952        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3953            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 3954        });
 3955        editor.join_lines(&JoinLines, window, cx);
 3956        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 3957        assert_eq!(
 3958            editor.selections.ranges::<Point>(cx),
 3959            &[Point::new(0, 11)..Point::new(0, 11)]
 3960        );
 3961
 3962        // Undo should be transactional
 3963        editor.undo(&Undo, window, cx);
 3964        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3965        assert_eq!(
 3966            editor.selections.ranges::<Point>(cx),
 3967            &[Point::new(0, 5)..Point::new(2, 2)]
 3968        );
 3969
 3970        // When joining an empty line don't insert a space
 3971        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3972            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 3973        });
 3974        editor.join_lines(&JoinLines, window, cx);
 3975        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 3976        assert_eq!(
 3977            editor.selections.ranges::<Point>(cx),
 3978            [Point::new(2, 3)..Point::new(2, 3)]
 3979        );
 3980
 3981        // We can remove trailing newlines
 3982        editor.join_lines(&JoinLines, window, cx);
 3983        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3984        assert_eq!(
 3985            editor.selections.ranges::<Point>(cx),
 3986            [Point::new(2, 3)..Point::new(2, 3)]
 3987        );
 3988
 3989        // We don't blow up on the last line
 3990        editor.join_lines(&JoinLines, window, cx);
 3991        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3992        assert_eq!(
 3993            editor.selections.ranges::<Point>(cx),
 3994            [Point::new(2, 3)..Point::new(2, 3)]
 3995        );
 3996
 3997        // reset to test indentation
 3998        editor.buffer.update(cx, |buffer, cx| {
 3999            buffer.edit(
 4000                [
 4001                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 4002                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 4003                ],
 4004                None,
 4005                cx,
 4006            )
 4007        });
 4008
 4009        // We remove any leading spaces
 4010        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 4011        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4012            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 4013        });
 4014        editor.join_lines(&JoinLines, window, cx);
 4015        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 4016
 4017        // We don't insert a space for a line containing only spaces
 4018        editor.join_lines(&JoinLines, window, cx);
 4019        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 4020
 4021        // We ignore any leading tabs
 4022        editor.join_lines(&JoinLines, window, cx);
 4023        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 4024
 4025        editor
 4026    });
 4027}
 4028
 4029#[gpui::test]
 4030fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 4031    init_test(cx, |_| {});
 4032
 4033    cx.add_window(|window, cx| {
 4034        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4035        let mut editor = build_editor(buffer.clone(), window, cx);
 4036        let buffer = buffer.read(cx).as_singleton().unwrap();
 4037
 4038        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4039            s.select_ranges([
 4040                Point::new(0, 2)..Point::new(1, 1),
 4041                Point::new(1, 2)..Point::new(1, 2),
 4042                Point::new(3, 1)..Point::new(3, 2),
 4043            ])
 4044        });
 4045
 4046        editor.join_lines(&JoinLines, window, cx);
 4047        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 4048
 4049        assert_eq!(
 4050            editor.selections.ranges::<Point>(cx),
 4051            [
 4052                Point::new(0, 7)..Point::new(0, 7),
 4053                Point::new(1, 3)..Point::new(1, 3)
 4054            ]
 4055        );
 4056        editor
 4057    });
 4058}
 4059
 4060#[gpui::test]
 4061async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4062    init_test(cx, |_| {});
 4063
 4064    let mut cx = EditorTestContext::new(cx).await;
 4065
 4066    let diff_base = r#"
 4067        Line 0
 4068        Line 1
 4069        Line 2
 4070        Line 3
 4071        "#
 4072    .unindent();
 4073
 4074    cx.set_state(
 4075        &r#"
 4076        ˇLine 0
 4077        Line 1
 4078        Line 2
 4079        Line 3
 4080        "#
 4081        .unindent(),
 4082    );
 4083
 4084    cx.set_head_text(&diff_base);
 4085    executor.run_until_parked();
 4086
 4087    // Join lines
 4088    cx.update_editor(|editor, window, cx| {
 4089        editor.join_lines(&JoinLines, window, cx);
 4090    });
 4091    executor.run_until_parked();
 4092
 4093    cx.assert_editor_state(
 4094        &r#"
 4095        Line 0ˇ Line 1
 4096        Line 2
 4097        Line 3
 4098        "#
 4099        .unindent(),
 4100    );
 4101    // Join again
 4102    cx.update_editor(|editor, window, cx| {
 4103        editor.join_lines(&JoinLines, window, cx);
 4104    });
 4105    executor.run_until_parked();
 4106
 4107    cx.assert_editor_state(
 4108        &r#"
 4109        Line 0 Line 1ˇ Line 2
 4110        Line 3
 4111        "#
 4112        .unindent(),
 4113    );
 4114}
 4115
 4116#[gpui::test]
 4117async fn test_custom_newlines_cause_no_false_positive_diffs(
 4118    executor: BackgroundExecutor,
 4119    cx: &mut TestAppContext,
 4120) {
 4121    init_test(cx, |_| {});
 4122    let mut cx = EditorTestContext::new(cx).await;
 4123    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4124    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4125    executor.run_until_parked();
 4126
 4127    cx.update_editor(|editor, window, cx| {
 4128        let snapshot = editor.snapshot(window, cx);
 4129        assert_eq!(
 4130            snapshot
 4131                .buffer_snapshot
 4132                .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
 4133                .collect::<Vec<_>>(),
 4134            Vec::new(),
 4135            "Should not have any diffs for files with custom newlines"
 4136        );
 4137    });
 4138}
 4139
 4140#[gpui::test]
 4141async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4142    init_test(cx, |_| {});
 4143
 4144    let mut cx = EditorTestContext::new(cx).await;
 4145
 4146    // Test sort_lines_case_insensitive()
 4147    cx.set_state(indoc! {"
 4148        «z
 4149        y
 4150        x
 4151        Z
 4152        Y
 4153        Xˇ»
 4154    "});
 4155    cx.update_editor(|e, window, cx| {
 4156        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4157    });
 4158    cx.assert_editor_state(indoc! {"
 4159        «x
 4160        X
 4161        y
 4162        Y
 4163        z
 4164        Zˇ»
 4165    "});
 4166
 4167    // Test sort_lines_by_length()
 4168    //
 4169    // Demonstrates:
 4170    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4171    // - sort is stable
 4172    cx.set_state(indoc! {"
 4173        «123
 4174        æ
 4175        12
 4176 4177        1
 4178        æˇ»
 4179    "});
 4180    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4181    cx.assert_editor_state(indoc! {"
 4182        «æ
 4183 4184        1
 4185        æ
 4186        12
 4187        123ˇ»
 4188    "});
 4189
 4190    // Test reverse_lines()
 4191    cx.set_state(indoc! {"
 4192        «5
 4193        4
 4194        3
 4195        2
 4196        1ˇ»
 4197    "});
 4198    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4199    cx.assert_editor_state(indoc! {"
 4200        «1
 4201        2
 4202        3
 4203        4
 4204        5ˇ»
 4205    "});
 4206
 4207    // Skip testing shuffle_line()
 4208
 4209    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4210    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4211
 4212    // Don't manipulate when cursor is on single line, but expand the selection
 4213    cx.set_state(indoc! {"
 4214        ddˇdd
 4215        ccc
 4216        bb
 4217        a
 4218    "});
 4219    cx.update_editor(|e, window, cx| {
 4220        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4221    });
 4222    cx.assert_editor_state(indoc! {"
 4223        «ddddˇ»
 4224        ccc
 4225        bb
 4226        a
 4227    "});
 4228
 4229    // Basic manipulate case
 4230    // Start selection moves to column 0
 4231    // End of selection shrinks to fit shorter line
 4232    cx.set_state(indoc! {"
 4233        dd«d
 4234        ccc
 4235        bb
 4236        aaaaaˇ»
 4237    "});
 4238    cx.update_editor(|e, window, cx| {
 4239        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4240    });
 4241    cx.assert_editor_state(indoc! {"
 4242        «aaaaa
 4243        bb
 4244        ccc
 4245        dddˇ»
 4246    "});
 4247
 4248    // Manipulate case with newlines
 4249    cx.set_state(indoc! {"
 4250        dd«d
 4251        ccc
 4252
 4253        bb
 4254        aaaaa
 4255
 4256        ˇ»
 4257    "});
 4258    cx.update_editor(|e, window, cx| {
 4259        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4260    });
 4261    cx.assert_editor_state(indoc! {"
 4262        «
 4263
 4264        aaaaa
 4265        bb
 4266        ccc
 4267        dddˇ»
 4268
 4269    "});
 4270
 4271    // Adding new line
 4272    cx.set_state(indoc! {"
 4273        aa«a
 4274        bbˇ»b
 4275    "});
 4276    cx.update_editor(|e, window, cx| {
 4277        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4278    });
 4279    cx.assert_editor_state(indoc! {"
 4280        «aaa
 4281        bbb
 4282        added_lineˇ»
 4283    "});
 4284
 4285    // Removing line
 4286    cx.set_state(indoc! {"
 4287        aa«a
 4288        bbbˇ»
 4289    "});
 4290    cx.update_editor(|e, window, cx| {
 4291        e.manipulate_immutable_lines(window, cx, |lines| {
 4292            lines.pop();
 4293        })
 4294    });
 4295    cx.assert_editor_state(indoc! {"
 4296        «aaaˇ»
 4297    "});
 4298
 4299    // Removing all lines
 4300    cx.set_state(indoc! {"
 4301        aa«a
 4302        bbbˇ»
 4303    "});
 4304    cx.update_editor(|e, window, cx| {
 4305        e.manipulate_immutable_lines(window, cx, |lines| {
 4306            lines.drain(..);
 4307        })
 4308    });
 4309    cx.assert_editor_state(indoc! {"
 4310        ˇ
 4311    "});
 4312}
 4313
 4314#[gpui::test]
 4315async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4316    init_test(cx, |_| {});
 4317
 4318    let mut cx = EditorTestContext::new(cx).await;
 4319
 4320    // Consider continuous selection as single selection
 4321    cx.set_state(indoc! {"
 4322        Aaa«aa
 4323        cˇ»c«c
 4324        bb
 4325        aaaˇ»aa
 4326    "});
 4327    cx.update_editor(|e, window, cx| {
 4328        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4329    });
 4330    cx.assert_editor_state(indoc! {"
 4331        «Aaaaa
 4332        ccc
 4333        bb
 4334        aaaaaˇ»
 4335    "});
 4336
 4337    cx.set_state(indoc! {"
 4338        Aaa«aa
 4339        cˇ»c«c
 4340        bb
 4341        aaaˇ»aa
 4342    "});
 4343    cx.update_editor(|e, window, cx| {
 4344        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4345    });
 4346    cx.assert_editor_state(indoc! {"
 4347        «Aaaaa
 4348        ccc
 4349        bbˇ»
 4350    "});
 4351
 4352    // Consider non continuous selection as distinct dedup operations
 4353    cx.set_state(indoc! {"
 4354        «aaaaa
 4355        bb
 4356        aaaaa
 4357        aaaaaˇ»
 4358
 4359        aaa«aaˇ»
 4360    "});
 4361    cx.update_editor(|e, window, cx| {
 4362        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4363    });
 4364    cx.assert_editor_state(indoc! {"
 4365        «aaaaa
 4366        bbˇ»
 4367
 4368        «aaaaaˇ»
 4369    "});
 4370}
 4371
 4372#[gpui::test]
 4373async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4374    init_test(cx, |_| {});
 4375
 4376    let mut cx = EditorTestContext::new(cx).await;
 4377
 4378    cx.set_state(indoc! {"
 4379        «Aaa
 4380        aAa
 4381        Aaaˇ»
 4382    "});
 4383    cx.update_editor(|e, window, cx| {
 4384        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4385    });
 4386    cx.assert_editor_state(indoc! {"
 4387        «Aaa
 4388        aAaˇ»
 4389    "});
 4390
 4391    cx.set_state(indoc! {"
 4392        «Aaa
 4393        aAa
 4394        aaAˇ»
 4395    "});
 4396    cx.update_editor(|e, window, cx| {
 4397        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4398    });
 4399    cx.assert_editor_state(indoc! {"
 4400        «Aaaˇ»
 4401    "});
 4402}
 4403
 4404#[gpui::test]
 4405async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 4406    init_test(cx, |_| {});
 4407
 4408    let mut cx = EditorTestContext::new(cx).await;
 4409
 4410    // Manipulate with multiple selections on a single line
 4411    cx.set_state(indoc! {"
 4412        dd«dd
 4413        cˇ»c«c
 4414        bb
 4415        aaaˇ»aa
 4416    "});
 4417    cx.update_editor(|e, window, cx| {
 4418        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4419    });
 4420    cx.assert_editor_state(indoc! {"
 4421        «aaaaa
 4422        bb
 4423        ccc
 4424        ddddˇ»
 4425    "});
 4426
 4427    // Manipulate with multiple disjoin selections
 4428    cx.set_state(indoc! {"
 4429 4430        4
 4431        3
 4432        2
 4433        1ˇ»
 4434
 4435        dd«dd
 4436        ccc
 4437        bb
 4438        aaaˇ»aa
 4439    "});
 4440    cx.update_editor(|e, window, cx| {
 4441        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4442    });
 4443    cx.assert_editor_state(indoc! {"
 4444        «1
 4445        2
 4446        3
 4447        4
 4448        5ˇ»
 4449
 4450        «aaaaa
 4451        bb
 4452        ccc
 4453        ddddˇ»
 4454    "});
 4455
 4456    // Adding lines on each selection
 4457    cx.set_state(indoc! {"
 4458 4459        1ˇ»
 4460
 4461        bb«bb
 4462        aaaˇ»aa
 4463    "});
 4464    cx.update_editor(|e, window, cx| {
 4465        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 4466    });
 4467    cx.assert_editor_state(indoc! {"
 4468        «2
 4469        1
 4470        added lineˇ»
 4471
 4472        «bbbb
 4473        aaaaa
 4474        added lineˇ»
 4475    "});
 4476
 4477    // Removing lines on each selection
 4478    cx.set_state(indoc! {"
 4479 4480        1ˇ»
 4481
 4482        bb«bb
 4483        aaaˇ»aa
 4484    "});
 4485    cx.update_editor(|e, window, cx| {
 4486        e.manipulate_immutable_lines(window, cx, |lines| {
 4487            lines.pop();
 4488        })
 4489    });
 4490    cx.assert_editor_state(indoc! {"
 4491        «2ˇ»
 4492
 4493        «bbbbˇ»
 4494    "});
 4495}
 4496
 4497#[gpui::test]
 4498async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 4499    init_test(cx, |settings| {
 4500        settings.defaults.tab_size = NonZeroU32::new(3)
 4501    });
 4502
 4503    let mut cx = EditorTestContext::new(cx).await;
 4504
 4505    // MULTI SELECTION
 4506    // Ln.1 "«" tests empty lines
 4507    // Ln.9 tests just leading whitespace
 4508    cx.set_state(indoc! {"
 4509        «
 4510        abc                 // No indentationˇ»
 4511        «\tabc              // 1 tabˇ»
 4512        \t\tabc «      ˇ»   // 2 tabs
 4513        \t ab«c             // Tab followed by space
 4514         \tabc              // Space followed by tab (3 spaces should be the result)
 4515        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4516           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 4517        \t
 4518        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4519    "});
 4520    cx.update_editor(|e, window, cx| {
 4521        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4522    });
 4523    cx.assert_editor_state(
 4524        indoc! {"
 4525            «
 4526            abc                 // No indentation
 4527               abc              // 1 tab
 4528                  abc          // 2 tabs
 4529                abc             // Tab followed by space
 4530               abc              // Space followed by tab (3 spaces should be the result)
 4531                           abc   // Mixed indentation (tab conversion depends on the column)
 4532               abc         // Already space indented
 4533               ·
 4534               abc\tdef          // Only the leading tab is manipulatedˇ»
 4535        "}
 4536        .replace("·", "")
 4537        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4538    );
 4539
 4540    // Test on just a few lines, the others should remain unchanged
 4541    // Only lines (3, 5, 10, 11) should change
 4542    cx.set_state(
 4543        indoc! {"
 4544            ·
 4545            abc                 // No indentation
 4546            \tabcˇ               // 1 tab
 4547            \t\tabc             // 2 tabs
 4548            \t abcˇ              // Tab followed by space
 4549             \tabc              // Space followed by tab (3 spaces should be the result)
 4550            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4551               abc              // Already space indented
 4552            «\t
 4553            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4554        "}
 4555        .replace("·", "")
 4556        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4557    );
 4558    cx.update_editor(|e, window, cx| {
 4559        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4560    });
 4561    cx.assert_editor_state(
 4562        indoc! {"
 4563            ·
 4564            abc                 // No indentation
 4565            «   abc               // 1 tabˇ»
 4566            \t\tabc             // 2 tabs
 4567            «    abc              // Tab followed by spaceˇ»
 4568             \tabc              // Space followed by tab (3 spaces should be the result)
 4569            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4570               abc              // Already space indented
 4571            «   ·
 4572               abc\tdef          // Only the leading tab is manipulatedˇ»
 4573        "}
 4574        .replace("·", "")
 4575        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4576    );
 4577
 4578    // SINGLE SELECTION
 4579    // Ln.1 "«" tests empty lines
 4580    // Ln.9 tests just leading whitespace
 4581    cx.set_state(indoc! {"
 4582        «
 4583        abc                 // No indentation
 4584        \tabc               // 1 tab
 4585        \t\tabc             // 2 tabs
 4586        \t abc              // Tab followed by space
 4587         \tabc              // Space followed by tab (3 spaces should be the result)
 4588        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4589           abc              // Already space indented
 4590        \t
 4591        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4592    "});
 4593    cx.update_editor(|e, window, cx| {
 4594        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4595    });
 4596    cx.assert_editor_state(
 4597        indoc! {"
 4598            «
 4599            abc                 // No indentation
 4600               abc               // 1 tab
 4601                  abc             // 2 tabs
 4602                abc              // Tab followed by space
 4603               abc              // Space followed by tab (3 spaces should be the result)
 4604                           abc   // Mixed indentation (tab conversion depends on the column)
 4605               abc              // Already space indented
 4606               ·
 4607               abc\tdef          // Only the leading tab is manipulatedˇ»
 4608        "}
 4609        .replace("·", "")
 4610        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4611    );
 4612}
 4613
 4614#[gpui::test]
 4615async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 4616    init_test(cx, |settings| {
 4617        settings.defaults.tab_size = NonZeroU32::new(3)
 4618    });
 4619
 4620    let mut cx = EditorTestContext::new(cx).await;
 4621
 4622    // MULTI SELECTION
 4623    // Ln.1 "«" tests empty lines
 4624    // Ln.11 tests just leading whitespace
 4625    cx.set_state(indoc! {"
 4626        «
 4627        abˇ»ˇc                 // No indentation
 4628         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 4629          abc  «             // 2 spaces (< 3 so dont convert)
 4630           abc              // 3 spaces (convert)
 4631             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 4632        «\tˇ»\t«\tˇ»abc           // Already tab indented
 4633        «\t abc              // Tab followed by space
 4634         \tabc              // Space followed by tab (should be consumed due to tab)
 4635        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4636           \tˇ»  «\t
 4637           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 4638    "});
 4639    cx.update_editor(|e, window, cx| {
 4640        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4641    });
 4642    cx.assert_editor_state(indoc! {"
 4643        «
 4644        abc                 // No indentation
 4645         abc                // 1 space (< 3 so dont convert)
 4646          abc               // 2 spaces (< 3 so dont convert)
 4647        \tabc              // 3 spaces (convert)
 4648        \t  abc            // 5 spaces (1 tab + 2 spaces)
 4649        \t\t\tabc           // Already tab indented
 4650        \t abc              // Tab followed by space
 4651        \tabc              // Space followed by tab (should be consumed due to tab)
 4652        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4653        \t\t\t
 4654        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4655    "});
 4656
 4657    // Test on just a few lines, the other should remain unchanged
 4658    // Only lines (4, 8, 11, 12) should change
 4659    cx.set_state(
 4660        indoc! {"
 4661            ·
 4662            abc                 // No indentation
 4663             abc                // 1 space (< 3 so dont convert)
 4664              abc               // 2 spaces (< 3 so dont convert)
 4665            «   abc              // 3 spaces (convert)ˇ»
 4666                 abc            // 5 spaces (1 tab + 2 spaces)
 4667            \t\t\tabc           // Already tab indented
 4668            \t abc              // Tab followed by space
 4669             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 4670               \t\t  \tabc      // Mixed indentation
 4671            \t \t  \t   \tabc   // Mixed indentation
 4672               \t  \tˇ
 4673            «   abc   \t         // Only the leading spaces should be convertedˇ»
 4674        "}
 4675        .replace("·", "")
 4676        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4677    );
 4678    cx.update_editor(|e, window, cx| {
 4679        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4680    });
 4681    cx.assert_editor_state(
 4682        indoc! {"
 4683            ·
 4684            abc                 // No indentation
 4685             abc                // 1 space (< 3 so dont convert)
 4686              abc               // 2 spaces (< 3 so dont convert)
 4687            «\tabc              // 3 spaces (convert)ˇ»
 4688                 abc            // 5 spaces (1 tab + 2 spaces)
 4689            \t\t\tabc           // Already tab indented
 4690            \t abc              // Tab followed by space
 4691            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 4692               \t\t  \tabc      // Mixed indentation
 4693            \t \t  \t   \tabc   // Mixed indentation
 4694            «\t\t\t
 4695            \tabc   \t         // Only the leading spaces should be convertedˇ»
 4696        "}
 4697        .replace("·", "")
 4698        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4699    );
 4700
 4701    // SINGLE SELECTION
 4702    // Ln.1 "«" tests empty lines
 4703    // Ln.11 tests just leading whitespace
 4704    cx.set_state(indoc! {"
 4705        «
 4706        abc                 // No indentation
 4707         abc                // 1 space (< 3 so dont convert)
 4708          abc               // 2 spaces (< 3 so dont convert)
 4709           abc              // 3 spaces (convert)
 4710             abc            // 5 spaces (1 tab + 2 spaces)
 4711        \t\t\tabc           // Already tab indented
 4712        \t abc              // Tab followed by space
 4713         \tabc              // Space followed by tab (should be consumed due to tab)
 4714        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4715           \t  \t
 4716           abc   \t         // Only the leading spaces should be convertedˇ»
 4717    "});
 4718    cx.update_editor(|e, window, cx| {
 4719        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4720    });
 4721    cx.assert_editor_state(indoc! {"
 4722        «
 4723        abc                 // No indentation
 4724         abc                // 1 space (< 3 so dont convert)
 4725          abc               // 2 spaces (< 3 so dont convert)
 4726        \tabc              // 3 spaces (convert)
 4727        \t  abc            // 5 spaces (1 tab + 2 spaces)
 4728        \t\t\tabc           // Already tab indented
 4729        \t abc              // Tab followed by space
 4730        \tabc              // Space followed by tab (should be consumed due to tab)
 4731        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4732        \t\t\t
 4733        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4734    "});
 4735}
 4736
 4737#[gpui::test]
 4738async fn test_toggle_case(cx: &mut TestAppContext) {
 4739    init_test(cx, |_| {});
 4740
 4741    let mut cx = EditorTestContext::new(cx).await;
 4742
 4743    // If all lower case -> upper case
 4744    cx.set_state(indoc! {"
 4745        «hello worldˇ»
 4746    "});
 4747    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4748    cx.assert_editor_state(indoc! {"
 4749        «HELLO WORLDˇ»
 4750    "});
 4751
 4752    // If all upper case -> lower case
 4753    cx.set_state(indoc! {"
 4754        «HELLO WORLDˇ»
 4755    "});
 4756    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4757    cx.assert_editor_state(indoc! {"
 4758        «hello worldˇ»
 4759    "});
 4760
 4761    // If any upper case characters are identified -> lower case
 4762    // This matches JetBrains IDEs
 4763    cx.set_state(indoc! {"
 4764        «hEllo worldˇ»
 4765    "});
 4766    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4767    cx.assert_editor_state(indoc! {"
 4768        «hello worldˇ»
 4769    "});
 4770}
 4771
 4772#[gpui::test]
 4773async fn test_convert_to_sentence_case(cx: &mut TestAppContext) {
 4774    init_test(cx, |_| {});
 4775
 4776    let mut cx = EditorTestContext::new(cx).await;
 4777
 4778    cx.set_state(indoc! {"
 4779        «implement-windows-supportˇ»
 4780    "});
 4781    cx.update_editor(|e, window, cx| {
 4782        e.convert_to_sentence_case(&ConvertToSentenceCase, window, cx)
 4783    });
 4784    cx.assert_editor_state(indoc! {"
 4785        «Implement windows supportˇ»
 4786    "});
 4787}
 4788
 4789#[gpui::test]
 4790async fn test_manipulate_text(cx: &mut TestAppContext) {
 4791    init_test(cx, |_| {});
 4792
 4793    let mut cx = EditorTestContext::new(cx).await;
 4794
 4795    // Test convert_to_upper_case()
 4796    cx.set_state(indoc! {"
 4797        «hello worldˇ»
 4798    "});
 4799    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4800    cx.assert_editor_state(indoc! {"
 4801        «HELLO WORLDˇ»
 4802    "});
 4803
 4804    // Test convert_to_lower_case()
 4805    cx.set_state(indoc! {"
 4806        «HELLO WORLDˇ»
 4807    "});
 4808    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 4809    cx.assert_editor_state(indoc! {"
 4810        «hello worldˇ»
 4811    "});
 4812
 4813    // Test multiple line, single selection case
 4814    cx.set_state(indoc! {"
 4815        «The quick brown
 4816        fox jumps over
 4817        the lazy dogˇ»
 4818    "});
 4819    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 4820    cx.assert_editor_state(indoc! {"
 4821        «The Quick Brown
 4822        Fox Jumps Over
 4823        The Lazy Dogˇ»
 4824    "});
 4825
 4826    // Test multiple line, single selection case
 4827    cx.set_state(indoc! {"
 4828        «The quick brown
 4829        fox jumps over
 4830        the lazy dogˇ»
 4831    "});
 4832    cx.update_editor(|e, window, cx| {
 4833        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 4834    });
 4835    cx.assert_editor_state(indoc! {"
 4836        «TheQuickBrown
 4837        FoxJumpsOver
 4838        TheLazyDogˇ»
 4839    "});
 4840
 4841    // From here on out, test more complex cases of manipulate_text()
 4842
 4843    // Test no selection case - should affect words cursors are in
 4844    // Cursor at beginning, middle, and end of word
 4845    cx.set_state(indoc! {"
 4846        ˇhello big beauˇtiful worldˇ
 4847    "});
 4848    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4849    cx.assert_editor_state(indoc! {"
 4850        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 4851    "});
 4852
 4853    // Test multiple selections on a single line and across multiple lines
 4854    cx.set_state(indoc! {"
 4855        «Theˇ» quick «brown
 4856        foxˇ» jumps «overˇ»
 4857        the «lazyˇ» dog
 4858    "});
 4859    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4860    cx.assert_editor_state(indoc! {"
 4861        «THEˇ» quick «BROWN
 4862        FOXˇ» jumps «OVERˇ»
 4863        the «LAZYˇ» dog
 4864    "});
 4865
 4866    // Test case where text length grows
 4867    cx.set_state(indoc! {"
 4868        «tschüߡ»
 4869    "});
 4870    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4871    cx.assert_editor_state(indoc! {"
 4872        «TSCHÜSSˇ»
 4873    "});
 4874
 4875    // Test to make sure we don't crash when text shrinks
 4876    cx.set_state(indoc! {"
 4877        aaa_bbbˇ
 4878    "});
 4879    cx.update_editor(|e, window, cx| {
 4880        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4881    });
 4882    cx.assert_editor_state(indoc! {"
 4883        «aaaBbbˇ»
 4884    "});
 4885
 4886    // Test to make sure we all aware of the fact that each word can grow and shrink
 4887    // Final selections should be aware of this fact
 4888    cx.set_state(indoc! {"
 4889        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 4890    "});
 4891    cx.update_editor(|e, window, cx| {
 4892        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4893    });
 4894    cx.assert_editor_state(indoc! {"
 4895        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 4896    "});
 4897
 4898    cx.set_state(indoc! {"
 4899        «hElLo, WoRld!ˇ»
 4900    "});
 4901    cx.update_editor(|e, window, cx| {
 4902        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 4903    });
 4904    cx.assert_editor_state(indoc! {"
 4905        «HeLlO, wOrLD!ˇ»
 4906    "});
 4907}
 4908
 4909#[gpui::test]
 4910fn test_duplicate_line(cx: &mut TestAppContext) {
 4911    init_test(cx, |_| {});
 4912
 4913    let editor = cx.add_window(|window, cx| {
 4914        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4915        build_editor(buffer, window, cx)
 4916    });
 4917    _ = editor.update(cx, |editor, window, cx| {
 4918        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4919            s.select_display_ranges([
 4920                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4921                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4922                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4923                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4924            ])
 4925        });
 4926        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4927        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4928        assert_eq!(
 4929            editor.selections.display_ranges(cx),
 4930            vec![
 4931                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4932                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 4933                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4934                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4935            ]
 4936        );
 4937    });
 4938
 4939    let editor = cx.add_window(|window, cx| {
 4940        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4941        build_editor(buffer, window, cx)
 4942    });
 4943    _ = editor.update(cx, |editor, window, cx| {
 4944        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4945            s.select_display_ranges([
 4946                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4947                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4948            ])
 4949        });
 4950        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4951        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4952        assert_eq!(
 4953            editor.selections.display_ranges(cx),
 4954            vec![
 4955                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 4956                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 4957            ]
 4958        );
 4959    });
 4960
 4961    // With `move_upwards` the selections stay in place, except for
 4962    // the lines inserted above them
 4963    let editor = cx.add_window(|window, cx| {
 4964        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4965        build_editor(buffer, window, cx)
 4966    });
 4967    _ = editor.update(cx, |editor, window, cx| {
 4968        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4969            s.select_display_ranges([
 4970                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4971                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4972                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4973                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4974            ])
 4975        });
 4976        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4977        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4978        assert_eq!(
 4979            editor.selections.display_ranges(cx),
 4980            vec![
 4981                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4982                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4983                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 4984                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4985            ]
 4986        );
 4987    });
 4988
 4989    let editor = cx.add_window(|window, cx| {
 4990        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4991        build_editor(buffer, window, cx)
 4992    });
 4993    _ = editor.update(cx, |editor, window, cx| {
 4994        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4995            s.select_display_ranges([
 4996                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4997                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4998            ])
 4999        });
 5000        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5001        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5002        assert_eq!(
 5003            editor.selections.display_ranges(cx),
 5004            vec![
 5005                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5006                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5007            ]
 5008        );
 5009    });
 5010
 5011    let editor = cx.add_window(|window, cx| {
 5012        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5013        build_editor(buffer, window, cx)
 5014    });
 5015    _ = editor.update(cx, |editor, window, cx| {
 5016        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5017            s.select_display_ranges([
 5018                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5019                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5020            ])
 5021        });
 5022        editor.duplicate_selection(&DuplicateSelection, window, cx);
 5023        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 5024        assert_eq!(
 5025            editor.selections.display_ranges(cx),
 5026            vec![
 5027                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5028                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 5029            ]
 5030        );
 5031    });
 5032}
 5033
 5034#[gpui::test]
 5035fn test_move_line_up_down(cx: &mut TestAppContext) {
 5036    init_test(cx, |_| {});
 5037
 5038    let editor = cx.add_window(|window, cx| {
 5039        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5040        build_editor(buffer, window, cx)
 5041    });
 5042    _ = editor.update(cx, |editor, window, cx| {
 5043        editor.fold_creases(
 5044            vec![
 5045                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5046                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5047                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5048            ],
 5049            true,
 5050            window,
 5051            cx,
 5052        );
 5053        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5054            s.select_display_ranges([
 5055                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5056                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5057                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5058                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 5059            ])
 5060        });
 5061        assert_eq!(
 5062            editor.display_text(cx),
 5063            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 5064        );
 5065
 5066        editor.move_line_up(&MoveLineUp, window, cx);
 5067        assert_eq!(
 5068            editor.display_text(cx),
 5069            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 5070        );
 5071        assert_eq!(
 5072            editor.selections.display_ranges(cx),
 5073            vec![
 5074                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5075                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5076                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5077                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5078            ]
 5079        );
 5080    });
 5081
 5082    _ = editor.update(cx, |editor, window, cx| {
 5083        editor.move_line_down(&MoveLineDown, window, cx);
 5084        assert_eq!(
 5085            editor.display_text(cx),
 5086            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 5087        );
 5088        assert_eq!(
 5089            editor.selections.display_ranges(cx),
 5090            vec![
 5091                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5092                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5093                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5094                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5095            ]
 5096        );
 5097    });
 5098
 5099    _ = editor.update(cx, |editor, window, cx| {
 5100        editor.move_line_down(&MoveLineDown, window, cx);
 5101        assert_eq!(
 5102            editor.display_text(cx),
 5103            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 5104        );
 5105        assert_eq!(
 5106            editor.selections.display_ranges(cx),
 5107            vec![
 5108                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5109                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5110                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5111                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5112            ]
 5113        );
 5114    });
 5115
 5116    _ = editor.update(cx, |editor, window, cx| {
 5117        editor.move_line_up(&MoveLineUp, window, cx);
 5118        assert_eq!(
 5119            editor.display_text(cx),
 5120            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 5121        );
 5122        assert_eq!(
 5123            editor.selections.display_ranges(cx),
 5124            vec![
 5125                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5126                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5127                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5128                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5129            ]
 5130        );
 5131    });
 5132}
 5133
 5134#[gpui::test]
 5135fn test_move_line_up_selection_at_end_of_fold(cx: &mut TestAppContext) {
 5136    init_test(cx, |_| {});
 5137    let editor = cx.add_window(|window, cx| {
 5138        let buffer = MultiBuffer::build_simple("\n\n\n\n\n\naaaa\nbbbb\ncccc", cx);
 5139        build_editor(buffer, window, cx)
 5140    });
 5141    _ = editor.update(cx, |editor, window, cx| {
 5142        editor.fold_creases(
 5143            vec![Crease::simple(
 5144                Point::new(6, 4)..Point::new(7, 4),
 5145                FoldPlaceholder::test(),
 5146            )],
 5147            true,
 5148            window,
 5149            cx,
 5150        );
 5151        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5152            s.select_ranges([Point::new(7, 4)..Point::new(7, 4)])
 5153        });
 5154        assert_eq!(editor.display_text(cx), "\n\n\n\n\n\naaaa⋯\ncccc");
 5155        editor.move_line_up(&MoveLineUp, window, cx);
 5156        let buffer_text = editor.buffer.read(cx).snapshot(cx).text();
 5157        assert_eq!(buffer_text, "\n\n\n\n\naaaa\nbbbb\n\ncccc");
 5158    });
 5159}
 5160
 5161#[gpui::test]
 5162fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 5163    init_test(cx, |_| {});
 5164
 5165    let editor = cx.add_window(|window, cx| {
 5166        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5167        build_editor(buffer, window, cx)
 5168    });
 5169    _ = editor.update(cx, |editor, window, cx| {
 5170        let snapshot = editor.buffer.read(cx).snapshot(cx);
 5171        editor.insert_blocks(
 5172            [BlockProperties {
 5173                style: BlockStyle::Fixed,
 5174                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 5175                height: Some(1),
 5176                render: Arc::new(|_| div().into_any()),
 5177                priority: 0,
 5178            }],
 5179            Some(Autoscroll::fit()),
 5180            cx,
 5181        );
 5182        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5183            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 5184        });
 5185        editor.move_line_down(&MoveLineDown, window, cx);
 5186    });
 5187}
 5188
 5189#[gpui::test]
 5190async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 5191    init_test(cx, |_| {});
 5192
 5193    let mut cx = EditorTestContext::new(cx).await;
 5194    cx.set_state(
 5195        &"
 5196            ˇzero
 5197            one
 5198            two
 5199            three
 5200            four
 5201            five
 5202        "
 5203        .unindent(),
 5204    );
 5205
 5206    // Create a four-line block that replaces three lines of text.
 5207    cx.update_editor(|editor, window, cx| {
 5208        let snapshot = editor.snapshot(window, cx);
 5209        let snapshot = &snapshot.buffer_snapshot;
 5210        let placement = BlockPlacement::Replace(
 5211            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 5212        );
 5213        editor.insert_blocks(
 5214            [BlockProperties {
 5215                placement,
 5216                height: Some(4),
 5217                style: BlockStyle::Sticky,
 5218                render: Arc::new(|_| gpui::div().into_any_element()),
 5219                priority: 0,
 5220            }],
 5221            None,
 5222            cx,
 5223        );
 5224    });
 5225
 5226    // Move down so that the cursor touches the block.
 5227    cx.update_editor(|editor, window, cx| {
 5228        editor.move_down(&Default::default(), window, cx);
 5229    });
 5230    cx.assert_editor_state(
 5231        &"
 5232            zero
 5233            «one
 5234            two
 5235            threeˇ»
 5236            four
 5237            five
 5238        "
 5239        .unindent(),
 5240    );
 5241
 5242    // Move down past the block.
 5243    cx.update_editor(|editor, window, cx| {
 5244        editor.move_down(&Default::default(), window, cx);
 5245    });
 5246    cx.assert_editor_state(
 5247        &"
 5248            zero
 5249            one
 5250            two
 5251            three
 5252            ˇfour
 5253            five
 5254        "
 5255        .unindent(),
 5256    );
 5257}
 5258
 5259#[gpui::test]
 5260fn test_transpose(cx: &mut TestAppContext) {
 5261    init_test(cx, |_| {});
 5262
 5263    _ = cx.add_window(|window, cx| {
 5264        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 5265        editor.set_style(EditorStyle::default(), window, cx);
 5266        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5267            s.select_ranges([1..1])
 5268        });
 5269        editor.transpose(&Default::default(), window, cx);
 5270        assert_eq!(editor.text(cx), "bac");
 5271        assert_eq!(editor.selections.ranges(cx), [2..2]);
 5272
 5273        editor.transpose(&Default::default(), window, cx);
 5274        assert_eq!(editor.text(cx), "bca");
 5275        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5276
 5277        editor.transpose(&Default::default(), window, cx);
 5278        assert_eq!(editor.text(cx), "bac");
 5279        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5280
 5281        editor
 5282    });
 5283
 5284    _ = cx.add_window(|window, cx| {
 5285        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5286        editor.set_style(EditorStyle::default(), window, cx);
 5287        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5288            s.select_ranges([3..3])
 5289        });
 5290        editor.transpose(&Default::default(), window, cx);
 5291        assert_eq!(editor.text(cx), "acb\nde");
 5292        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5293
 5294        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5295            s.select_ranges([4..4])
 5296        });
 5297        editor.transpose(&Default::default(), window, cx);
 5298        assert_eq!(editor.text(cx), "acbd\ne");
 5299        assert_eq!(editor.selections.ranges(cx), [5..5]);
 5300
 5301        editor.transpose(&Default::default(), window, cx);
 5302        assert_eq!(editor.text(cx), "acbde\n");
 5303        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5304
 5305        editor.transpose(&Default::default(), window, cx);
 5306        assert_eq!(editor.text(cx), "acbd\ne");
 5307        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5308
 5309        editor
 5310    });
 5311
 5312    _ = cx.add_window(|window, cx| {
 5313        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5314        editor.set_style(EditorStyle::default(), window, cx);
 5315        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5316            s.select_ranges([1..1, 2..2, 4..4])
 5317        });
 5318        editor.transpose(&Default::default(), window, cx);
 5319        assert_eq!(editor.text(cx), "bacd\ne");
 5320        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 5321
 5322        editor.transpose(&Default::default(), window, cx);
 5323        assert_eq!(editor.text(cx), "bcade\n");
 5324        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 5325
 5326        editor.transpose(&Default::default(), window, cx);
 5327        assert_eq!(editor.text(cx), "bcda\ne");
 5328        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5329
 5330        editor.transpose(&Default::default(), window, cx);
 5331        assert_eq!(editor.text(cx), "bcade\n");
 5332        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5333
 5334        editor.transpose(&Default::default(), window, cx);
 5335        assert_eq!(editor.text(cx), "bcaed\n");
 5336        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 5337
 5338        editor
 5339    });
 5340
 5341    _ = cx.add_window(|window, cx| {
 5342        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 5343        editor.set_style(EditorStyle::default(), window, cx);
 5344        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5345            s.select_ranges([4..4])
 5346        });
 5347        editor.transpose(&Default::default(), window, cx);
 5348        assert_eq!(editor.text(cx), "🏀🍐✋");
 5349        assert_eq!(editor.selections.ranges(cx), [8..8]);
 5350
 5351        editor.transpose(&Default::default(), window, cx);
 5352        assert_eq!(editor.text(cx), "🏀✋🍐");
 5353        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5354
 5355        editor.transpose(&Default::default(), window, cx);
 5356        assert_eq!(editor.text(cx), "🏀🍐✋");
 5357        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5358
 5359        editor
 5360    });
 5361}
 5362
 5363#[gpui::test]
 5364async fn test_rewrap(cx: &mut TestAppContext) {
 5365    init_test(cx, |settings| {
 5366        settings.languages.0.extend([
 5367            (
 5368                "Markdown".into(),
 5369                LanguageSettingsContent {
 5370                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5371                    preferred_line_length: Some(40),
 5372                    ..Default::default()
 5373                },
 5374            ),
 5375            (
 5376                "Plain Text".into(),
 5377                LanguageSettingsContent {
 5378                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5379                    preferred_line_length: Some(40),
 5380                    ..Default::default()
 5381                },
 5382            ),
 5383            (
 5384                "C++".into(),
 5385                LanguageSettingsContent {
 5386                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5387                    preferred_line_length: Some(40),
 5388                    ..Default::default()
 5389                },
 5390            ),
 5391            (
 5392                "Python".into(),
 5393                LanguageSettingsContent {
 5394                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5395                    preferred_line_length: Some(40),
 5396                    ..Default::default()
 5397                },
 5398            ),
 5399            (
 5400                "Rust".into(),
 5401                LanguageSettingsContent {
 5402                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5403                    preferred_line_length: Some(40),
 5404                    ..Default::default()
 5405                },
 5406            ),
 5407        ])
 5408    });
 5409
 5410    let mut cx = EditorTestContext::new(cx).await;
 5411
 5412    let cpp_language = Arc::new(Language::new(
 5413        LanguageConfig {
 5414            name: "C++".into(),
 5415            line_comments: vec!["// ".into()],
 5416            ..LanguageConfig::default()
 5417        },
 5418        None,
 5419    ));
 5420    let python_language = Arc::new(Language::new(
 5421        LanguageConfig {
 5422            name: "Python".into(),
 5423            line_comments: vec!["# ".into()],
 5424            ..LanguageConfig::default()
 5425        },
 5426        None,
 5427    ));
 5428    let markdown_language = Arc::new(Language::new(
 5429        LanguageConfig {
 5430            name: "Markdown".into(),
 5431            rewrap_prefixes: vec![
 5432                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 5433                regex::Regex::new("[-*+]\\s+").unwrap(),
 5434            ],
 5435            ..LanguageConfig::default()
 5436        },
 5437        None,
 5438    ));
 5439    let rust_language = Arc::new(Language::new(
 5440        LanguageConfig {
 5441            name: "Rust".into(),
 5442            line_comments: vec!["// ".into(), "/// ".into()],
 5443            ..LanguageConfig::default()
 5444        },
 5445        Some(tree_sitter_rust::LANGUAGE.into()),
 5446    ));
 5447
 5448    let plaintext_language = Arc::new(Language::new(
 5449        LanguageConfig {
 5450            name: "Plain Text".into(),
 5451            ..LanguageConfig::default()
 5452        },
 5453        None,
 5454    ));
 5455
 5456    // Test basic rewrapping of a long line with a cursor
 5457    assert_rewrap(
 5458        indoc! {"
 5459            // ˇThis is a long comment that needs to be wrapped.
 5460        "},
 5461        indoc! {"
 5462            // ˇThis is a long comment that needs to
 5463            // be wrapped.
 5464        "},
 5465        cpp_language.clone(),
 5466        &mut cx,
 5467    );
 5468
 5469    // Test rewrapping a full selection
 5470    assert_rewrap(
 5471        indoc! {"
 5472            «// This selected long comment needs to be wrapped.ˇ»"
 5473        },
 5474        indoc! {"
 5475            «// This selected long comment needs to
 5476            // be wrapped.ˇ»"
 5477        },
 5478        cpp_language.clone(),
 5479        &mut cx,
 5480    );
 5481
 5482    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 5483    assert_rewrap(
 5484        indoc! {"
 5485            // ˇThis is the first line.
 5486            // Thisˇ is the second line.
 5487            // This is the thirdˇ line, all part of one paragraph.
 5488         "},
 5489        indoc! {"
 5490            // ˇThis is the first line. Thisˇ is the
 5491            // second line. This is the thirdˇ line,
 5492            // all part of one paragraph.
 5493         "},
 5494        cpp_language.clone(),
 5495        &mut cx,
 5496    );
 5497
 5498    // Test multiple cursors in different paragraphs trigger separate rewraps
 5499    assert_rewrap(
 5500        indoc! {"
 5501            // ˇThis is the first paragraph, first line.
 5502            // ˇThis is the first paragraph, second line.
 5503
 5504            // ˇThis is the second paragraph, first line.
 5505            // ˇThis is the second paragraph, second line.
 5506        "},
 5507        indoc! {"
 5508            // ˇThis is the first paragraph, first
 5509            // line. ˇThis is the first paragraph,
 5510            // second line.
 5511
 5512            // ˇThis is the second paragraph, first
 5513            // line. ˇThis is the second paragraph,
 5514            // second line.
 5515        "},
 5516        cpp_language.clone(),
 5517        &mut cx,
 5518    );
 5519
 5520    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 5521    assert_rewrap(
 5522        indoc! {"
 5523            «// A regular long long comment to be wrapped.
 5524            /// A documentation long comment to be wrapped.ˇ»
 5525          "},
 5526        indoc! {"
 5527            «// A regular long long comment to be
 5528            // wrapped.
 5529            /// A documentation long comment to be
 5530            /// wrapped.ˇ»
 5531          "},
 5532        rust_language.clone(),
 5533        &mut cx,
 5534    );
 5535
 5536    // Test that change in indentation level trigger seperate rewraps
 5537    assert_rewrap(
 5538        indoc! {"
 5539            fn foo() {
 5540                «// This is a long comment at the base indent.
 5541                    // This is a long comment at the next indent.ˇ»
 5542            }
 5543        "},
 5544        indoc! {"
 5545            fn foo() {
 5546                «// This is a long comment at the
 5547                // base indent.
 5548                    // This is a long comment at the
 5549                    // next indent.ˇ»
 5550            }
 5551        "},
 5552        rust_language.clone(),
 5553        &mut cx,
 5554    );
 5555
 5556    // Test that different comment prefix characters (e.g., '#') are handled correctly
 5557    assert_rewrap(
 5558        indoc! {"
 5559            # ˇThis is a long comment using a pound sign.
 5560        "},
 5561        indoc! {"
 5562            # ˇThis is a long comment using a pound
 5563            # sign.
 5564        "},
 5565        python_language.clone(),
 5566        &mut cx,
 5567    );
 5568
 5569    // Test rewrapping only affects comments, not code even when selected
 5570    assert_rewrap(
 5571        indoc! {"
 5572            «/// This doc comment is long and should be wrapped.
 5573            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 5574        "},
 5575        indoc! {"
 5576            «/// This doc comment is long and should
 5577            /// be wrapped.
 5578            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 5579        "},
 5580        rust_language.clone(),
 5581        &mut cx,
 5582    );
 5583
 5584    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 5585    assert_rewrap(
 5586        indoc! {"
 5587            # Header
 5588
 5589            A long long long line of markdown text to wrap.ˇ
 5590         "},
 5591        indoc! {"
 5592            # Header
 5593
 5594            A long long long line of markdown text
 5595            to wrap.ˇ
 5596         "},
 5597        markdown_language.clone(),
 5598        &mut cx,
 5599    );
 5600
 5601    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 5602    assert_rewrap(
 5603        indoc! {"
 5604            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 5605            2. This is a numbered list item that is very long and needs to be wrapped properly.
 5606            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 5607        "},
 5608        indoc! {"
 5609            «1. This is a numbered list item that is
 5610               very long and needs to be wrapped
 5611               properly.
 5612            2. This is a numbered list item that is
 5613               very long and needs to be wrapped
 5614               properly.
 5615            - This is an unordered list item that is
 5616              also very long and should not merge
 5617              with the numbered item.ˇ»
 5618        "},
 5619        markdown_language.clone(),
 5620        &mut cx,
 5621    );
 5622
 5623    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 5624    assert_rewrap(
 5625        indoc! {"
 5626            «1. This is a numbered list item that is
 5627            very long and needs to be wrapped
 5628            properly.
 5629            2. This is a numbered list item that is
 5630            very long and needs to be wrapped
 5631            properly.
 5632            - This is an unordered list item that is
 5633            also very long and should not merge with
 5634            the numbered item.ˇ»
 5635        "},
 5636        indoc! {"
 5637            «1. This is a numbered list item that is
 5638               very long and needs to be wrapped
 5639               properly.
 5640            2. This is a numbered list item that is
 5641               very long and needs to be wrapped
 5642               properly.
 5643            - This is an unordered list item that is
 5644              also very long and should not merge
 5645              with the numbered item.ˇ»
 5646        "},
 5647        markdown_language.clone(),
 5648        &mut cx,
 5649    );
 5650
 5651    // Test that rewrapping maintain indents even when they already exists.
 5652    assert_rewrap(
 5653        indoc! {"
 5654            «1. This is a numbered list
 5655               item that is very long and needs to be wrapped properly.
 5656            2. This is a numbered list
 5657               item that is very long and needs to be wrapped properly.
 5658            - This is an unordered list item that is also very long and
 5659              should not merge with the numbered item.ˇ»
 5660        "},
 5661        indoc! {"
 5662            «1. This is a numbered list item that is
 5663               very long and needs to be wrapped
 5664               properly.
 5665            2. This is a numbered list item that is
 5666               very long and needs to be wrapped
 5667               properly.
 5668            - This is an unordered list item that is
 5669              also very long and should not merge
 5670              with the numbered item.ˇ»
 5671        "},
 5672        markdown_language.clone(),
 5673        &mut cx,
 5674    );
 5675
 5676    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 5677    assert_rewrap(
 5678        indoc! {"
 5679            ˇThis is a very long line of plain text that will be wrapped.
 5680        "},
 5681        indoc! {"
 5682            ˇThis is a very long line of plain text
 5683            that will be wrapped.
 5684        "},
 5685        plaintext_language.clone(),
 5686        &mut cx,
 5687    );
 5688
 5689    // Test that non-commented code acts as a paragraph boundary within a selection
 5690    assert_rewrap(
 5691        indoc! {"
 5692               «// This is the first long comment block to be wrapped.
 5693               fn my_func(a: u32);
 5694               // This is the second long comment block to be wrapped.ˇ»
 5695           "},
 5696        indoc! {"
 5697               «// This is the first long comment block
 5698               // to be wrapped.
 5699               fn my_func(a: u32);
 5700               // This is the second long comment block
 5701               // to be wrapped.ˇ»
 5702           "},
 5703        rust_language.clone(),
 5704        &mut cx,
 5705    );
 5706
 5707    // Test rewrapping multiple selections, including ones with blank lines or tabs
 5708    assert_rewrap(
 5709        indoc! {"
 5710            «ˇThis is a very long line that will be wrapped.
 5711
 5712            This is another paragraph in the same selection.»
 5713
 5714            «\tThis is a very long indented line that will be wrapped.ˇ»
 5715         "},
 5716        indoc! {"
 5717            «ˇThis is a very long line that will be
 5718            wrapped.
 5719
 5720            This is another paragraph in the same
 5721            selection.»
 5722
 5723            «\tThis is a very long indented line
 5724            \tthat will be wrapped.ˇ»
 5725         "},
 5726        plaintext_language.clone(),
 5727        &mut cx,
 5728    );
 5729
 5730    // Test that an empty comment line acts as a paragraph boundary
 5731    assert_rewrap(
 5732        indoc! {"
 5733            // ˇThis is a long comment that will be wrapped.
 5734            //
 5735            // And this is another long comment that will also be wrapped.ˇ
 5736         "},
 5737        indoc! {"
 5738            // ˇThis is a long comment that will be
 5739            // wrapped.
 5740            //
 5741            // And this is another long comment that
 5742            // will also be wrapped.ˇ
 5743         "},
 5744        cpp_language,
 5745        &mut cx,
 5746    );
 5747
 5748    #[track_caller]
 5749    fn assert_rewrap(
 5750        unwrapped_text: &str,
 5751        wrapped_text: &str,
 5752        language: Arc<Language>,
 5753        cx: &mut EditorTestContext,
 5754    ) {
 5755        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 5756        cx.set_state(unwrapped_text);
 5757        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 5758        cx.assert_editor_state(wrapped_text);
 5759    }
 5760}
 5761
 5762#[gpui::test]
 5763async fn test_hard_wrap(cx: &mut TestAppContext) {
 5764    init_test(cx, |_| {});
 5765    let mut cx = EditorTestContext::new(cx).await;
 5766
 5767    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 5768    cx.update_editor(|editor, _, cx| {
 5769        editor.set_hard_wrap(Some(14), cx);
 5770    });
 5771
 5772    cx.set_state(indoc!(
 5773        "
 5774        one two three ˇ
 5775        "
 5776    ));
 5777    cx.simulate_input("four");
 5778    cx.run_until_parked();
 5779
 5780    cx.assert_editor_state(indoc!(
 5781        "
 5782        one two three
 5783        fourˇ
 5784        "
 5785    ));
 5786
 5787    cx.update_editor(|editor, window, cx| {
 5788        editor.newline(&Default::default(), window, cx);
 5789    });
 5790    cx.run_until_parked();
 5791    cx.assert_editor_state(indoc!(
 5792        "
 5793        one two three
 5794        four
 5795        ˇ
 5796        "
 5797    ));
 5798
 5799    cx.simulate_input("five");
 5800    cx.run_until_parked();
 5801    cx.assert_editor_state(indoc!(
 5802        "
 5803        one two three
 5804        four
 5805        fiveˇ
 5806        "
 5807    ));
 5808
 5809    cx.update_editor(|editor, window, cx| {
 5810        editor.newline(&Default::default(), window, cx);
 5811    });
 5812    cx.run_until_parked();
 5813    cx.simulate_input("# ");
 5814    cx.run_until_parked();
 5815    cx.assert_editor_state(indoc!(
 5816        "
 5817        one two three
 5818        four
 5819        five
 5820        # ˇ
 5821        "
 5822    ));
 5823
 5824    cx.update_editor(|editor, window, cx| {
 5825        editor.newline(&Default::default(), window, cx);
 5826    });
 5827    cx.run_until_parked();
 5828    cx.assert_editor_state(indoc!(
 5829        "
 5830        one two three
 5831        four
 5832        five
 5833        #\x20
 5834 5835        "
 5836    ));
 5837
 5838    cx.simulate_input(" 6");
 5839    cx.run_until_parked();
 5840    cx.assert_editor_state(indoc!(
 5841        "
 5842        one two three
 5843        four
 5844        five
 5845        #
 5846        # 6ˇ
 5847        "
 5848    ));
 5849}
 5850
 5851#[gpui::test]
 5852async fn test_clipboard(cx: &mut TestAppContext) {
 5853    init_test(cx, |_| {});
 5854
 5855    let mut cx = EditorTestContext::new(cx).await;
 5856
 5857    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 5858    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5859    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 5860
 5861    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 5862    cx.set_state("two ˇfour ˇsix ˇ");
 5863    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5864    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 5865
 5866    // Paste again but with only two cursors. Since the number of cursors doesn't
 5867    // match the number of slices in the clipboard, the entire clipboard text
 5868    // is pasted at each cursor.
 5869    cx.set_state("ˇtwo one✅ four three six five ˇ");
 5870    cx.update_editor(|e, window, cx| {
 5871        e.handle_input("( ", window, cx);
 5872        e.paste(&Paste, window, cx);
 5873        e.handle_input(") ", window, cx);
 5874    });
 5875    cx.assert_editor_state(
 5876        &([
 5877            "( one✅ ",
 5878            "three ",
 5879            "five ) ˇtwo one✅ four three six five ( one✅ ",
 5880            "three ",
 5881            "five ) ˇ",
 5882        ]
 5883        .join("\n")),
 5884    );
 5885
 5886    // Cut with three selections, one of which is full-line.
 5887    cx.set_state(indoc! {"
 5888        1«2ˇ»3
 5889        4ˇ567
 5890        «8ˇ»9"});
 5891    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5892    cx.assert_editor_state(indoc! {"
 5893        1ˇ3
 5894        ˇ9"});
 5895
 5896    // Paste with three selections, noticing how the copied selection that was full-line
 5897    // gets inserted before the second cursor.
 5898    cx.set_state(indoc! {"
 5899        1ˇ3
 5900 5901        «oˇ»ne"});
 5902    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5903    cx.assert_editor_state(indoc! {"
 5904        12ˇ3
 5905        4567
 5906 5907        8ˇne"});
 5908
 5909    // Copy with a single cursor only, which writes the whole line into the clipboard.
 5910    cx.set_state(indoc! {"
 5911        The quick brown
 5912        fox juˇmps over
 5913        the lazy dog"});
 5914    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5915    assert_eq!(
 5916        cx.read_from_clipboard()
 5917            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5918        Some("fox jumps over\n".to_string())
 5919    );
 5920
 5921    // Paste with three selections, noticing how the copied full-line selection is inserted
 5922    // before the empty selections but replaces the selection that is non-empty.
 5923    cx.set_state(indoc! {"
 5924        Tˇhe quick brown
 5925        «foˇ»x jumps over
 5926        tˇhe lazy dog"});
 5927    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5928    cx.assert_editor_state(indoc! {"
 5929        fox jumps over
 5930        Tˇhe quick brown
 5931        fox jumps over
 5932        ˇx jumps over
 5933        fox jumps over
 5934        tˇhe lazy dog"});
 5935}
 5936
 5937#[gpui::test]
 5938async fn test_copy_trim(cx: &mut TestAppContext) {
 5939    init_test(cx, |_| {});
 5940
 5941    let mut cx = EditorTestContext::new(cx).await;
 5942    cx.set_state(
 5943        r#"            «for selection in selections.iter() {
 5944            let mut start = selection.start;
 5945            let mut end = selection.end;
 5946            let is_entire_line = selection.is_empty();
 5947            if is_entire_line {
 5948                start = Point::new(start.row, 0);ˇ»
 5949                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5950            }
 5951        "#,
 5952    );
 5953    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5954    assert_eq!(
 5955        cx.read_from_clipboard()
 5956            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5957        Some(
 5958            "for selection in selections.iter() {
 5959            let mut start = selection.start;
 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                .to_string()
 5965        ),
 5966        "Regular copying preserves all indentation selected",
 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;
 5975let mut end = selection.end;
 5976let is_entire_line = selection.is_empty();
 5977if is_entire_line {
 5978    start = Point::new(start.row, 0);"
 5979                .to_string()
 5980        ),
 5981        "Copying with stripping should strip all leading whitespaces"
 5982    );
 5983
 5984    cx.set_state(
 5985        r#"       «     for selection in selections.iter() {
 5986            let mut start = selection.start;
 5987            let mut end = selection.end;
 5988            let is_entire_line = selection.is_empty();
 5989            if is_entire_line {
 5990                start = Point::new(start.row, 0);ˇ»
 5991                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5992            }
 5993        "#,
 5994    );
 5995    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5996    assert_eq!(
 5997        cx.read_from_clipboard()
 5998            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5999        Some(
 6000            "     for selection in selections.iter() {
 6001            let mut start = selection.start;
 6002            let mut end = selection.end;
 6003            let is_entire_line = selection.is_empty();
 6004            if is_entire_line {
 6005                start = Point::new(start.row, 0);"
 6006                .to_string()
 6007        ),
 6008        "Regular copying preserves all indentation selected",
 6009    );
 6010    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6011    assert_eq!(
 6012        cx.read_from_clipboard()
 6013            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6014        Some(
 6015            "for selection in selections.iter() {
 6016let mut start = selection.start;
 6017let mut end = selection.end;
 6018let is_entire_line = selection.is_empty();
 6019if is_entire_line {
 6020    start = Point::new(start.row, 0);"
 6021                .to_string()
 6022        ),
 6023        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 6024    );
 6025
 6026    cx.set_state(
 6027        r#"       «ˇ     for selection in selections.iter() {
 6028            let mut start = selection.start;
 6029            let mut end = selection.end;
 6030            let is_entire_line = selection.is_empty();
 6031            if is_entire_line {
 6032                start = Point::new(start.row, 0);»
 6033                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6034            }
 6035        "#,
 6036    );
 6037    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6038    assert_eq!(
 6039        cx.read_from_clipboard()
 6040            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6041        Some(
 6042            "     for selection in selections.iter() {
 6043            let mut start = selection.start;
 6044            let mut end = selection.end;
 6045            let is_entire_line = selection.is_empty();
 6046            if is_entire_line {
 6047                start = Point::new(start.row, 0);"
 6048                .to_string()
 6049        ),
 6050        "Regular copying for reverse selection works the same",
 6051    );
 6052    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6053    assert_eq!(
 6054        cx.read_from_clipboard()
 6055            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6056        Some(
 6057            "for selection in selections.iter() {
 6058let mut start = selection.start;
 6059let mut end = selection.end;
 6060let is_entire_line = selection.is_empty();
 6061if is_entire_line {
 6062    start = Point::new(start.row, 0);"
 6063                .to_string()
 6064        ),
 6065        "Copying with stripping for reverse selection works the same"
 6066    );
 6067
 6068    cx.set_state(
 6069        r#"            for selection «in selections.iter() {
 6070            let mut start = selection.start;
 6071            let mut end = selection.end;
 6072            let is_entire_line = selection.is_empty();
 6073            if is_entire_line {
 6074                start = Point::new(start.row, 0);ˇ»
 6075                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6076            }
 6077        "#,
 6078    );
 6079    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6080    assert_eq!(
 6081        cx.read_from_clipboard()
 6082            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6083        Some(
 6084            "in selections.iter() {
 6085            let mut start = selection.start;
 6086            let mut end = selection.end;
 6087            let is_entire_line = selection.is_empty();
 6088            if is_entire_line {
 6089                start = Point::new(start.row, 0);"
 6090                .to_string()
 6091        ),
 6092        "When selecting past the indent, the copying works as usual",
 6093    );
 6094    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6095    assert_eq!(
 6096        cx.read_from_clipboard()
 6097            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6098        Some(
 6099            "in selections.iter() {
 6100            let mut start = selection.start;
 6101            let mut end = selection.end;
 6102            let is_entire_line = selection.is_empty();
 6103            if is_entire_line {
 6104                start = Point::new(start.row, 0);"
 6105                .to_string()
 6106        ),
 6107        "When selecting past the indent, nothing is trimmed"
 6108    );
 6109
 6110    cx.set_state(
 6111        r#"            «for selection in selections.iter() {
 6112            let mut start = selection.start;
 6113
 6114            let mut end = selection.end;
 6115            let is_entire_line = selection.is_empty();
 6116            if is_entire_line {
 6117                start = Point::new(start.row, 0);
 6118ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6119            }
 6120        "#,
 6121    );
 6122    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6123    assert_eq!(
 6124        cx.read_from_clipboard()
 6125            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6126        Some(
 6127            "for selection in selections.iter() {
 6128let mut start = selection.start;
 6129
 6130let mut end = selection.end;
 6131let is_entire_line = selection.is_empty();
 6132if is_entire_line {
 6133    start = Point::new(start.row, 0);
 6134"
 6135            .to_string()
 6136        ),
 6137        "Copying with stripping should ignore empty lines"
 6138    );
 6139}
 6140
 6141#[gpui::test]
 6142async fn test_paste_multiline(cx: &mut TestAppContext) {
 6143    init_test(cx, |_| {});
 6144
 6145    let mut cx = EditorTestContext::new(cx).await;
 6146    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 6147
 6148    // Cut an indented block, without the leading whitespace.
 6149    cx.set_state(indoc! {"
 6150        const a: B = (
 6151            c(),
 6152            «d(
 6153                e,
 6154                f
 6155            )ˇ»
 6156        );
 6157    "});
 6158    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6159    cx.assert_editor_state(indoc! {"
 6160        const a: B = (
 6161            c(),
 6162            ˇ
 6163        );
 6164    "});
 6165
 6166    // Paste it at the same position.
 6167    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6168    cx.assert_editor_state(indoc! {"
 6169        const a: B = (
 6170            c(),
 6171            d(
 6172                e,
 6173                f
 6174 6175        );
 6176    "});
 6177
 6178    // Paste it at a line with a lower indent level.
 6179    cx.set_state(indoc! {"
 6180        ˇ
 6181        const a: B = (
 6182            c(),
 6183        );
 6184    "});
 6185    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6186    cx.assert_editor_state(indoc! {"
 6187        d(
 6188            e,
 6189            f
 6190 6191        const a: B = (
 6192            c(),
 6193        );
 6194    "});
 6195
 6196    // Cut an indented block, with the leading whitespace.
 6197    cx.set_state(indoc! {"
 6198        const a: B = (
 6199            c(),
 6200        «    d(
 6201                e,
 6202                f
 6203            )
 6204        ˇ»);
 6205    "});
 6206    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6207    cx.assert_editor_state(indoc! {"
 6208        const a: B = (
 6209            c(),
 6210        ˇ);
 6211    "});
 6212
 6213    // Paste it at the same position.
 6214    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6215    cx.assert_editor_state(indoc! {"
 6216        const a: B = (
 6217            c(),
 6218            d(
 6219                e,
 6220                f
 6221            )
 6222        ˇ);
 6223    "});
 6224
 6225    // Paste it at a line with a higher indent level.
 6226    cx.set_state(indoc! {"
 6227        const a: B = (
 6228            c(),
 6229            d(
 6230                e,
 6231 6232            )
 6233        );
 6234    "});
 6235    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6236    cx.assert_editor_state(indoc! {"
 6237        const a: B = (
 6238            c(),
 6239            d(
 6240                e,
 6241                f    d(
 6242                    e,
 6243                    f
 6244                )
 6245        ˇ
 6246            )
 6247        );
 6248    "});
 6249
 6250    // Copy an indented block, starting mid-line
 6251    cx.set_state(indoc! {"
 6252        const a: B = (
 6253            c(),
 6254            somethin«g(
 6255                e,
 6256                f
 6257            )ˇ»
 6258        );
 6259    "});
 6260    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6261
 6262    // Paste it on a line with a lower indent level
 6263    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 6264    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6265    cx.assert_editor_state(indoc! {"
 6266        const a: B = (
 6267            c(),
 6268            something(
 6269                e,
 6270                f
 6271            )
 6272        );
 6273        g(
 6274            e,
 6275            f
 6276"});
 6277}
 6278
 6279#[gpui::test]
 6280async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 6281    init_test(cx, |_| {});
 6282
 6283    cx.write_to_clipboard(ClipboardItem::new_string(
 6284        "    d(\n        e\n    );\n".into(),
 6285    ));
 6286
 6287    let mut cx = EditorTestContext::new(cx).await;
 6288    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 6289
 6290    cx.set_state(indoc! {"
 6291        fn a() {
 6292            b();
 6293            if c() {
 6294                ˇ
 6295            }
 6296        }
 6297    "});
 6298
 6299    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6300    cx.assert_editor_state(indoc! {"
 6301        fn a() {
 6302            b();
 6303            if c() {
 6304                d(
 6305                    e
 6306                );
 6307        ˇ
 6308            }
 6309        }
 6310    "});
 6311
 6312    cx.set_state(indoc! {"
 6313        fn a() {
 6314            b();
 6315            ˇ
 6316        }
 6317    "});
 6318
 6319    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6320    cx.assert_editor_state(indoc! {"
 6321        fn a() {
 6322            b();
 6323            d(
 6324                e
 6325            );
 6326        ˇ
 6327        }
 6328    "});
 6329}
 6330
 6331#[gpui::test]
 6332fn test_select_all(cx: &mut TestAppContext) {
 6333    init_test(cx, |_| {});
 6334
 6335    let editor = cx.add_window(|window, cx| {
 6336        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 6337        build_editor(buffer, window, cx)
 6338    });
 6339    _ = editor.update(cx, |editor, window, cx| {
 6340        editor.select_all(&SelectAll, window, cx);
 6341        assert_eq!(
 6342            editor.selections.display_ranges(cx),
 6343            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 6344        );
 6345    });
 6346}
 6347
 6348#[gpui::test]
 6349fn test_select_line(cx: &mut TestAppContext) {
 6350    init_test(cx, |_| {});
 6351
 6352    let editor = cx.add_window(|window, cx| {
 6353        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 6354        build_editor(buffer, window, cx)
 6355    });
 6356    _ = editor.update(cx, |editor, window, cx| {
 6357        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6358            s.select_display_ranges([
 6359                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6360                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6361                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6362                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 6363            ])
 6364        });
 6365        editor.select_line(&SelectLine, window, cx);
 6366        assert_eq!(
 6367            editor.selections.display_ranges(cx),
 6368            vec![
 6369                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 6370                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 6371            ]
 6372        );
 6373    });
 6374
 6375    _ = editor.update(cx, |editor, window, cx| {
 6376        editor.select_line(&SelectLine, window, cx);
 6377        assert_eq!(
 6378            editor.selections.display_ranges(cx),
 6379            vec![
 6380                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 6381                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 6382            ]
 6383        );
 6384    });
 6385
 6386    _ = editor.update(cx, |editor, window, cx| {
 6387        editor.select_line(&SelectLine, window, cx);
 6388        assert_eq!(
 6389            editor.selections.display_ranges(cx),
 6390            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 6391        );
 6392    });
 6393}
 6394
 6395#[gpui::test]
 6396async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 6397    init_test(cx, |_| {});
 6398    let mut cx = EditorTestContext::new(cx).await;
 6399
 6400    #[track_caller]
 6401    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 6402        cx.set_state(initial_state);
 6403        cx.update_editor(|e, window, cx| {
 6404            e.split_selection_into_lines(&Default::default(), window, cx)
 6405        });
 6406        cx.assert_editor_state(expected_state);
 6407    }
 6408
 6409    // Selection starts and ends at the middle of lines, left-to-right
 6410    test(
 6411        &mut cx,
 6412        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 6413        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 6414    );
 6415    // Same thing, right-to-left
 6416    test(
 6417        &mut cx,
 6418        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 6419        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 6420    );
 6421
 6422    // Whole buffer, left-to-right, last line *doesn't* end with newline
 6423    test(
 6424        &mut cx,
 6425        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 6426        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 6427    );
 6428    // Same thing, right-to-left
 6429    test(
 6430        &mut cx,
 6431        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 6432        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 6433    );
 6434
 6435    // Whole buffer, left-to-right, last line ends with newline
 6436    test(
 6437        &mut cx,
 6438        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 6439        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 6440    );
 6441    // Same thing, right-to-left
 6442    test(
 6443        &mut cx,
 6444        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 6445        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 6446    );
 6447
 6448    // Starts at the end of a line, ends at the start of another
 6449    test(
 6450        &mut cx,
 6451        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 6452        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 6453    );
 6454}
 6455
 6456#[gpui::test]
 6457async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 6458    init_test(cx, |_| {});
 6459
 6460    let editor = cx.add_window(|window, cx| {
 6461        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 6462        build_editor(buffer, window, cx)
 6463    });
 6464
 6465    // setup
 6466    _ = editor.update(cx, |editor, window, cx| {
 6467        editor.fold_creases(
 6468            vec![
 6469                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 6470                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 6471                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 6472            ],
 6473            true,
 6474            window,
 6475            cx,
 6476        );
 6477        assert_eq!(
 6478            editor.display_text(cx),
 6479            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 6480        );
 6481    });
 6482
 6483    _ = editor.update(cx, |editor, window, cx| {
 6484        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6485            s.select_display_ranges([
 6486                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6487                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6488                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6489                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 6490            ])
 6491        });
 6492        editor.split_selection_into_lines(&Default::default(), window, cx);
 6493        assert_eq!(
 6494            editor.display_text(cx),
 6495            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 6496        );
 6497    });
 6498    EditorTestContext::for_editor(editor, cx)
 6499        .await
 6500        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 6501
 6502    _ = editor.update(cx, |editor, window, cx| {
 6503        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6504            s.select_display_ranges([
 6505                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 6506            ])
 6507        });
 6508        editor.split_selection_into_lines(&Default::default(), window, cx);
 6509        assert_eq!(
 6510            editor.display_text(cx),
 6511            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 6512        );
 6513        assert_eq!(
 6514            editor.selections.display_ranges(cx),
 6515            [
 6516                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 6517                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 6518                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 6519                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 6520                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 6521                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 6522                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 6523            ]
 6524        );
 6525    });
 6526    EditorTestContext::for_editor(editor, cx)
 6527        .await
 6528        .assert_editor_state(
 6529            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 6530        );
 6531}
 6532
 6533#[gpui::test]
 6534async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 6535    init_test(cx, |_| {});
 6536
 6537    let mut cx = EditorTestContext::new(cx).await;
 6538
 6539    cx.set_state(indoc!(
 6540        r#"abc
 6541           defˇghi
 6542
 6543           jk
 6544           nlmo
 6545           "#
 6546    ));
 6547
 6548    cx.update_editor(|editor, window, cx| {
 6549        editor.add_selection_above(&Default::default(), window, cx);
 6550    });
 6551
 6552    cx.assert_editor_state(indoc!(
 6553        r#"abcˇ
 6554           defˇghi
 6555
 6556           jk
 6557           nlmo
 6558           "#
 6559    ));
 6560
 6561    cx.update_editor(|editor, window, cx| {
 6562        editor.add_selection_above(&Default::default(), window, cx);
 6563    });
 6564
 6565    cx.assert_editor_state(indoc!(
 6566        r#"abcˇ
 6567            defˇghi
 6568
 6569            jk
 6570            nlmo
 6571            "#
 6572    ));
 6573
 6574    cx.update_editor(|editor, window, cx| {
 6575        editor.add_selection_below(&Default::default(), window, cx);
 6576    });
 6577
 6578    cx.assert_editor_state(indoc!(
 6579        r#"abc
 6580           defˇghi
 6581
 6582           jk
 6583           nlmo
 6584           "#
 6585    ));
 6586
 6587    cx.update_editor(|editor, window, cx| {
 6588        editor.undo_selection(&Default::default(), window, cx);
 6589    });
 6590
 6591    cx.assert_editor_state(indoc!(
 6592        r#"abcˇ
 6593           defˇghi
 6594
 6595           jk
 6596           nlmo
 6597           "#
 6598    ));
 6599
 6600    cx.update_editor(|editor, window, cx| {
 6601        editor.redo_selection(&Default::default(), window, cx);
 6602    });
 6603
 6604    cx.assert_editor_state(indoc!(
 6605        r#"abc
 6606           defˇghi
 6607
 6608           jk
 6609           nlmo
 6610           "#
 6611    ));
 6612
 6613    cx.update_editor(|editor, window, cx| {
 6614        editor.add_selection_below(&Default::default(), window, cx);
 6615    });
 6616
 6617    cx.assert_editor_state(indoc!(
 6618        r#"abc
 6619           defˇghi
 6620           ˇ
 6621           jk
 6622           nlmo
 6623           "#
 6624    ));
 6625
 6626    cx.update_editor(|editor, window, cx| {
 6627        editor.add_selection_below(&Default::default(), window, cx);
 6628    });
 6629
 6630    cx.assert_editor_state(indoc!(
 6631        r#"abc
 6632           defˇghi
 6633           ˇ
 6634           jkˇ
 6635           nlmo
 6636           "#
 6637    ));
 6638
 6639    cx.update_editor(|editor, window, cx| {
 6640        editor.add_selection_below(&Default::default(), window, cx);
 6641    });
 6642
 6643    cx.assert_editor_state(indoc!(
 6644        r#"abc
 6645           defˇghi
 6646           ˇ
 6647           jkˇ
 6648           nlmˇo
 6649           "#
 6650    ));
 6651
 6652    cx.update_editor(|editor, window, cx| {
 6653        editor.add_selection_below(&Default::default(), window, cx);
 6654    });
 6655
 6656    cx.assert_editor_state(indoc!(
 6657        r#"abc
 6658           defˇghi
 6659           ˇ
 6660           jkˇ
 6661           nlmˇo
 6662           ˇ"#
 6663    ));
 6664
 6665    // change selections
 6666    cx.set_state(indoc!(
 6667        r#"abc
 6668           def«ˇg»hi
 6669
 6670           jk
 6671           nlmo
 6672           "#
 6673    ));
 6674
 6675    cx.update_editor(|editor, window, cx| {
 6676        editor.add_selection_below(&Default::default(), window, cx);
 6677    });
 6678
 6679    cx.assert_editor_state(indoc!(
 6680        r#"abc
 6681           def«ˇg»hi
 6682
 6683           jk
 6684           nlm«ˇo»
 6685           "#
 6686    ));
 6687
 6688    cx.update_editor(|editor, window, cx| {
 6689        editor.add_selection_below(&Default::default(), window, cx);
 6690    });
 6691
 6692    cx.assert_editor_state(indoc!(
 6693        r#"abc
 6694           def«ˇg»hi
 6695
 6696           jk
 6697           nlm«ˇo»
 6698           "#
 6699    ));
 6700
 6701    cx.update_editor(|editor, window, cx| {
 6702        editor.add_selection_above(&Default::default(), window, cx);
 6703    });
 6704
 6705    cx.assert_editor_state(indoc!(
 6706        r#"abc
 6707           def«ˇg»hi
 6708
 6709           jk
 6710           nlmo
 6711           "#
 6712    ));
 6713
 6714    cx.update_editor(|editor, window, cx| {
 6715        editor.add_selection_above(&Default::default(), window, cx);
 6716    });
 6717
 6718    cx.assert_editor_state(indoc!(
 6719        r#"abc
 6720           def«ˇg»hi
 6721
 6722           jk
 6723           nlmo
 6724           "#
 6725    ));
 6726
 6727    // Change selections again
 6728    cx.set_state(indoc!(
 6729        r#"a«bc
 6730           defgˇ»hi
 6731
 6732           jk
 6733           nlmo
 6734           "#
 6735    ));
 6736
 6737    cx.update_editor(|editor, window, cx| {
 6738        editor.add_selection_below(&Default::default(), window, cx);
 6739    });
 6740
 6741    cx.assert_editor_state(indoc!(
 6742        r#"a«bcˇ»
 6743           d«efgˇ»hi
 6744
 6745           j«kˇ»
 6746           nlmo
 6747           "#
 6748    ));
 6749
 6750    cx.update_editor(|editor, window, cx| {
 6751        editor.add_selection_below(&Default::default(), window, cx);
 6752    });
 6753    cx.assert_editor_state(indoc!(
 6754        r#"a«bcˇ»
 6755           d«efgˇ»hi
 6756
 6757           j«kˇ»
 6758           n«lmoˇ»
 6759           "#
 6760    ));
 6761    cx.update_editor(|editor, window, cx| {
 6762        editor.add_selection_above(&Default::default(), window, cx);
 6763    });
 6764
 6765    cx.assert_editor_state(indoc!(
 6766        r#"a«bcˇ»
 6767           d«efgˇ»hi
 6768
 6769           j«kˇ»
 6770           nlmo
 6771           "#
 6772    ));
 6773
 6774    // Change selections again
 6775    cx.set_state(indoc!(
 6776        r#"abc
 6777           d«ˇefghi
 6778
 6779           jk
 6780           nlm»o
 6781           "#
 6782    ));
 6783
 6784    cx.update_editor(|editor, window, cx| {
 6785        editor.add_selection_above(&Default::default(), window, cx);
 6786    });
 6787
 6788    cx.assert_editor_state(indoc!(
 6789        r#"a«ˇbc»
 6790           d«ˇef»ghi
 6791
 6792           j«ˇk»
 6793           n«ˇlm»o
 6794           "#
 6795    ));
 6796
 6797    cx.update_editor(|editor, window, cx| {
 6798        editor.add_selection_below(&Default::default(), window, cx);
 6799    });
 6800
 6801    cx.assert_editor_state(indoc!(
 6802        r#"abc
 6803           d«ˇef»ghi
 6804
 6805           j«ˇk»
 6806           n«ˇlm»o
 6807           "#
 6808    ));
 6809}
 6810
 6811#[gpui::test]
 6812async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 6813    init_test(cx, |_| {});
 6814    let mut cx = EditorTestContext::new(cx).await;
 6815
 6816    cx.set_state(indoc!(
 6817        r#"line onˇe
 6818           liˇne two
 6819           line three
 6820           line four"#
 6821    ));
 6822
 6823    cx.update_editor(|editor, window, cx| {
 6824        editor.add_selection_below(&Default::default(), window, cx);
 6825    });
 6826
 6827    // test multiple cursors expand in the same direction
 6828    cx.assert_editor_state(indoc!(
 6829        r#"line onˇe
 6830           liˇne twˇo
 6831           liˇne three
 6832           line four"#
 6833    ));
 6834
 6835    cx.update_editor(|editor, window, cx| {
 6836        editor.add_selection_below(&Default::default(), window, cx);
 6837    });
 6838
 6839    cx.update_editor(|editor, window, cx| {
 6840        editor.add_selection_below(&Default::default(), window, cx);
 6841    });
 6842
 6843    // test multiple cursors expand below overflow
 6844    cx.assert_editor_state(indoc!(
 6845        r#"line onˇe
 6846           liˇne twˇo
 6847           liˇne thˇree
 6848           liˇne foˇur"#
 6849    ));
 6850
 6851    cx.update_editor(|editor, window, cx| {
 6852        editor.add_selection_above(&Default::default(), window, cx);
 6853    });
 6854
 6855    // test multiple cursors retrieves back correctly
 6856    cx.assert_editor_state(indoc!(
 6857        r#"line onˇe
 6858           liˇne twˇo
 6859           liˇne thˇree
 6860           line four"#
 6861    ));
 6862
 6863    cx.update_editor(|editor, window, cx| {
 6864        editor.add_selection_above(&Default::default(), window, cx);
 6865    });
 6866
 6867    cx.update_editor(|editor, window, cx| {
 6868        editor.add_selection_above(&Default::default(), window, cx);
 6869    });
 6870
 6871    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 6872    cx.assert_editor_state(indoc!(
 6873        r#"liˇne onˇe
 6874           liˇne two
 6875           line three
 6876           line four"#
 6877    ));
 6878
 6879    cx.update_editor(|editor, window, cx| {
 6880        editor.undo_selection(&Default::default(), window, cx);
 6881    });
 6882
 6883    // test undo
 6884    cx.assert_editor_state(indoc!(
 6885        r#"line onˇe
 6886           liˇne twˇo
 6887           line three
 6888           line four"#
 6889    ));
 6890
 6891    cx.update_editor(|editor, window, cx| {
 6892        editor.redo_selection(&Default::default(), window, cx);
 6893    });
 6894
 6895    // test redo
 6896    cx.assert_editor_state(indoc!(
 6897        r#"liˇne onˇe
 6898           liˇne two
 6899           line three
 6900           line four"#
 6901    ));
 6902
 6903    cx.set_state(indoc!(
 6904        r#"abcd
 6905           ef«ghˇ»
 6906           ijkl
 6907           «mˇ»nop"#
 6908    ));
 6909
 6910    cx.update_editor(|editor, window, cx| {
 6911        editor.add_selection_above(&Default::default(), window, cx);
 6912    });
 6913
 6914    // test multiple selections expand in the same direction
 6915    cx.assert_editor_state(indoc!(
 6916        r#"ab«cdˇ»
 6917           ef«ghˇ»
 6918           «iˇ»jkl
 6919           «mˇ»nop"#
 6920    ));
 6921
 6922    cx.update_editor(|editor, window, cx| {
 6923        editor.add_selection_above(&Default::default(), window, cx);
 6924    });
 6925
 6926    // test multiple selection upward overflow
 6927    cx.assert_editor_state(indoc!(
 6928        r#"ab«cdˇ»
 6929           «eˇ»f«ghˇ»
 6930           «iˇ»jkl
 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 multiple selection retrieves back correctly
 6939    cx.assert_editor_state(indoc!(
 6940        r#"abcd
 6941           ef«ghˇ»
 6942           «iˇ»jkl
 6943           «mˇ»nop"#
 6944    ));
 6945
 6946    cx.update_editor(|editor, window, cx| {
 6947        editor.add_selection_below(&Default::default(), window, cx);
 6948    });
 6949
 6950    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 6951    cx.assert_editor_state(indoc!(
 6952        r#"abcd
 6953           ef«ghˇ»
 6954           ij«klˇ»
 6955           «mˇ»nop"#
 6956    ));
 6957
 6958    cx.update_editor(|editor, window, cx| {
 6959        editor.undo_selection(&Default::default(), window, cx);
 6960    });
 6961
 6962    // test undo
 6963    cx.assert_editor_state(indoc!(
 6964        r#"abcd
 6965           ef«ghˇ»
 6966           «iˇ»jkl
 6967           «mˇ»nop"#
 6968    ));
 6969
 6970    cx.update_editor(|editor, window, cx| {
 6971        editor.redo_selection(&Default::default(), window, cx);
 6972    });
 6973
 6974    // test redo
 6975    cx.assert_editor_state(indoc!(
 6976        r#"abcd
 6977           ef«ghˇ»
 6978           ij«klˇ»
 6979           «mˇ»nop"#
 6980    ));
 6981}
 6982
 6983#[gpui::test]
 6984async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 6985    init_test(cx, |_| {});
 6986    let mut cx = EditorTestContext::new(cx).await;
 6987
 6988    cx.set_state(indoc!(
 6989        r#"line onˇe
 6990           liˇne two
 6991           line three
 6992           line four"#
 6993    ));
 6994
 6995    cx.update_editor(|editor, window, cx| {
 6996        editor.add_selection_below(&Default::default(), window, cx);
 6997        editor.add_selection_below(&Default::default(), window, cx);
 6998        editor.add_selection_below(&Default::default(), window, cx);
 6999    });
 7000
 7001    // initial state with two multi cursor groups
 7002    cx.assert_editor_state(indoc!(
 7003        r#"line onˇe
 7004           liˇne twˇo
 7005           liˇne thˇree
 7006           liˇne foˇur"#
 7007    ));
 7008
 7009    // add single cursor in middle - simulate opt click
 7010    cx.update_editor(|editor, window, cx| {
 7011        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 7012        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 7013        editor.end_selection(window, cx);
 7014    });
 7015
 7016    cx.assert_editor_state(indoc!(
 7017        r#"line onˇe
 7018           liˇne twˇo
 7019           liˇneˇ thˇree
 7020           liˇne foˇur"#
 7021    ));
 7022
 7023    cx.update_editor(|editor, window, cx| {
 7024        editor.add_selection_above(&Default::default(), window, cx);
 7025    });
 7026
 7027    // test new added selection expands above and existing selection shrinks
 7028    cx.assert_editor_state(indoc!(
 7029        r#"line onˇe
 7030           liˇneˇ twˇo
 7031           liˇneˇ thˇree
 7032           line four"#
 7033    ));
 7034
 7035    cx.update_editor(|editor, window, cx| {
 7036        editor.add_selection_above(&Default::default(), window, cx);
 7037    });
 7038
 7039    // test new added selection expands above and existing selection shrinks
 7040    cx.assert_editor_state(indoc!(
 7041        r#"lineˇ onˇe
 7042           liˇneˇ twˇo
 7043           lineˇ three
 7044           line four"#
 7045    ));
 7046
 7047    // intial state with two selection groups
 7048    cx.set_state(indoc!(
 7049        r#"abcd
 7050           ef«ghˇ»
 7051           ijkl
 7052           «mˇ»nop"#
 7053    ));
 7054
 7055    cx.update_editor(|editor, window, cx| {
 7056        editor.add_selection_above(&Default::default(), window, cx);
 7057        editor.add_selection_above(&Default::default(), window, cx);
 7058    });
 7059
 7060    cx.assert_editor_state(indoc!(
 7061        r#"ab«cdˇ»
 7062           «eˇ»f«ghˇ»
 7063           «iˇ»jkl
 7064           «mˇ»nop"#
 7065    ));
 7066
 7067    // add single selection in middle - simulate opt drag
 7068    cx.update_editor(|editor, window, cx| {
 7069        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 7070        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 7071        editor.update_selection(
 7072            DisplayPoint::new(DisplayRow(2), 4),
 7073            0,
 7074            gpui::Point::<f32>::default(),
 7075            window,
 7076            cx,
 7077        );
 7078        editor.end_selection(window, cx);
 7079    });
 7080
 7081    cx.assert_editor_state(indoc!(
 7082        r#"ab«cdˇ»
 7083           «eˇ»f«ghˇ»
 7084           «iˇ»jk«lˇ»
 7085           «mˇ»nop"#
 7086    ));
 7087
 7088    cx.update_editor(|editor, window, cx| {
 7089        editor.add_selection_below(&Default::default(), window, cx);
 7090    });
 7091
 7092    // test new added selection expands below, others shrinks from above
 7093    cx.assert_editor_state(indoc!(
 7094        r#"abcd
 7095           ef«ghˇ»
 7096           «iˇ»jk«lˇ»
 7097           «mˇ»no«pˇ»"#
 7098    ));
 7099}
 7100
 7101#[gpui::test]
 7102async fn test_select_next(cx: &mut TestAppContext) {
 7103    init_test(cx, |_| {});
 7104
 7105    let mut cx = EditorTestContext::new(cx).await;
 7106    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7107
 7108    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7109        .unwrap();
 7110    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7111
 7112    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7113        .unwrap();
 7114    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 7115
 7116    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7117    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7118
 7119    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7120    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 7121
 7122    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7123        .unwrap();
 7124    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7125
 7126    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7127        .unwrap();
 7128    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7129
 7130    // Test selection direction should be preserved
 7131    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 7132
 7133    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7134        .unwrap();
 7135    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 7136}
 7137
 7138#[gpui::test]
 7139async fn test_select_all_matches(cx: &mut TestAppContext) {
 7140    init_test(cx, |_| {});
 7141
 7142    let mut cx = EditorTestContext::new(cx).await;
 7143
 7144    // Test caret-only selections
 7145    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7146    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7147        .unwrap();
 7148    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7149
 7150    // Test left-to-right selections
 7151    cx.set_state("abc\n«abcˇ»\nabc");
 7152    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7153        .unwrap();
 7154    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 7155
 7156    // Test right-to-left selections
 7157    cx.set_state("abc\n«ˇabc»\nabc");
 7158    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7159        .unwrap();
 7160    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 7161
 7162    // Test selecting whitespace with caret selection
 7163    cx.set_state("abc\nˇ   abc\nabc");
 7164    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7165        .unwrap();
 7166    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 7167
 7168    // Test selecting whitespace with left-to-right selection
 7169    cx.set_state("abc\n«ˇ  »abc\nabc");
 7170    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7171        .unwrap();
 7172    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 7173
 7174    // Test no matches with right-to-left selection
 7175    cx.set_state("abc\n«  ˇ»abc\nabc");
 7176    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7177        .unwrap();
 7178    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 7179
 7180    // Test with a single word and clip_at_line_ends=true (#29823)
 7181    cx.set_state("aˇbc");
 7182    cx.update_editor(|e, window, cx| {
 7183        e.set_clip_at_line_ends(true, cx);
 7184        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 7185        e.set_clip_at_line_ends(false, cx);
 7186    });
 7187    cx.assert_editor_state("«abcˇ»");
 7188}
 7189
 7190#[gpui::test]
 7191async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 7192    init_test(cx, |_| {});
 7193
 7194    let mut cx = EditorTestContext::new(cx).await;
 7195
 7196    let large_body_1 = "\nd".repeat(200);
 7197    let large_body_2 = "\ne".repeat(200);
 7198
 7199    cx.set_state(&format!(
 7200        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 7201    ));
 7202    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 7203        let scroll_position = editor.scroll_position(cx);
 7204        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 7205        scroll_position
 7206    });
 7207
 7208    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7209        .unwrap();
 7210    cx.assert_editor_state(&format!(
 7211        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 7212    ));
 7213    let scroll_position_after_selection =
 7214        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 7215    assert_eq!(
 7216        initial_scroll_position, scroll_position_after_selection,
 7217        "Scroll position should not change after selecting all matches"
 7218    );
 7219}
 7220
 7221#[gpui::test]
 7222async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 7223    init_test(cx, |_| {});
 7224
 7225    let mut cx = EditorLspTestContext::new_rust(
 7226        lsp::ServerCapabilities {
 7227            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 7228            ..Default::default()
 7229        },
 7230        cx,
 7231    )
 7232    .await;
 7233
 7234    cx.set_state(indoc! {"
 7235        line 1
 7236        line 2
 7237        linˇe 3
 7238        line 4
 7239        line 5
 7240    "});
 7241
 7242    // Make an edit
 7243    cx.update_editor(|editor, window, cx| {
 7244        editor.handle_input("X", window, cx);
 7245    });
 7246
 7247    // Move cursor to a different position
 7248    cx.update_editor(|editor, window, cx| {
 7249        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7250            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 7251        });
 7252    });
 7253
 7254    cx.assert_editor_state(indoc! {"
 7255        line 1
 7256        line 2
 7257        linXe 3
 7258        line 4
 7259        liˇne 5
 7260    "});
 7261
 7262    cx.lsp
 7263        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 7264            Ok(Some(vec![lsp::TextEdit::new(
 7265                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 7266                "PREFIX ".to_string(),
 7267            )]))
 7268        });
 7269
 7270    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 7271        .unwrap()
 7272        .await
 7273        .unwrap();
 7274
 7275    cx.assert_editor_state(indoc! {"
 7276        PREFIX line 1
 7277        line 2
 7278        linXe 3
 7279        line 4
 7280        liˇne 5
 7281    "});
 7282
 7283    // Undo formatting
 7284    cx.update_editor(|editor, window, cx| {
 7285        editor.undo(&Default::default(), window, cx);
 7286    });
 7287
 7288    // Verify cursor moved back to position after edit
 7289    cx.assert_editor_state(indoc! {"
 7290        line 1
 7291        line 2
 7292        linXˇe 3
 7293        line 4
 7294        line 5
 7295    "});
 7296}
 7297
 7298#[gpui::test]
 7299async fn test_undo_edit_prediction_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 7300    init_test(cx, |_| {});
 7301
 7302    let mut cx = EditorTestContext::new(cx).await;
 7303
 7304    let provider = cx.new(|_| FakeEditPredictionProvider::default());
 7305    cx.update_editor(|editor, window, cx| {
 7306        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 7307    });
 7308
 7309    cx.set_state(indoc! {"
 7310        line 1
 7311        line 2
 7312        linˇe 3
 7313        line 4
 7314        line 5
 7315        line 6
 7316        line 7
 7317        line 8
 7318        line 9
 7319        line 10
 7320    "});
 7321
 7322    let snapshot = cx.buffer_snapshot();
 7323    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 7324
 7325    cx.update(|_, cx| {
 7326        provider.update(cx, |provider, _| {
 7327            provider.set_edit_prediction(Some(edit_prediction::EditPrediction {
 7328                id: None,
 7329                edits: vec![(edit_position..edit_position, "X".into())],
 7330                edit_preview: None,
 7331            }))
 7332        })
 7333    });
 7334
 7335    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 7336    cx.update_editor(|editor, window, cx| {
 7337        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 7338    });
 7339
 7340    cx.assert_editor_state(indoc! {"
 7341        line 1
 7342        line 2
 7343        lineXˇ 3
 7344        line 4
 7345        line 5
 7346        line 6
 7347        line 7
 7348        line 8
 7349        line 9
 7350        line 10
 7351    "});
 7352
 7353    cx.update_editor(|editor, window, cx| {
 7354        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7355            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 7356        });
 7357    });
 7358
 7359    cx.assert_editor_state(indoc! {"
 7360        line 1
 7361        line 2
 7362        lineX 3
 7363        line 4
 7364        line 5
 7365        line 6
 7366        line 7
 7367        line 8
 7368        line 9
 7369        liˇne 10
 7370    "});
 7371
 7372    cx.update_editor(|editor, window, cx| {
 7373        editor.undo(&Default::default(), window, cx);
 7374    });
 7375
 7376    cx.assert_editor_state(indoc! {"
 7377        line 1
 7378        line 2
 7379        lineˇ 3
 7380        line 4
 7381        line 5
 7382        line 6
 7383        line 7
 7384        line 8
 7385        line 9
 7386        line 10
 7387    "});
 7388}
 7389
 7390#[gpui::test]
 7391async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 7392    init_test(cx, |_| {});
 7393
 7394    let mut cx = EditorTestContext::new(cx).await;
 7395    cx.set_state(
 7396        r#"let foo = 2;
 7397lˇet foo = 2;
 7398let fooˇ = 2;
 7399let foo = 2;
 7400let foo = ˇ2;"#,
 7401    );
 7402
 7403    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7404        .unwrap();
 7405    cx.assert_editor_state(
 7406        r#"let foo = 2;
 7407«letˇ» foo = 2;
 7408let «fooˇ» = 2;
 7409let foo = 2;
 7410let foo = «2ˇ»;"#,
 7411    );
 7412
 7413    // noop for multiple selections with different contents
 7414    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7415        .unwrap();
 7416    cx.assert_editor_state(
 7417        r#"let foo = 2;
 7418«letˇ» foo = 2;
 7419let «fooˇ» = 2;
 7420let foo = 2;
 7421let foo = «2ˇ»;"#,
 7422    );
 7423
 7424    // Test last selection direction should be preserved
 7425    cx.set_state(
 7426        r#"let foo = 2;
 7427let foo = 2;
 7428let «fooˇ» = 2;
 7429let «ˇfoo» = 2;
 7430let foo = 2;"#,
 7431    );
 7432
 7433    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7434        .unwrap();
 7435    cx.assert_editor_state(
 7436        r#"let foo = 2;
 7437let foo = 2;
 7438let «fooˇ» = 2;
 7439let «ˇfoo» = 2;
 7440let «ˇfoo» = 2;"#,
 7441    );
 7442}
 7443
 7444#[gpui::test]
 7445async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 7446    init_test(cx, |_| {});
 7447
 7448    let mut cx =
 7449        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 7450
 7451    cx.assert_editor_state(indoc! {"
 7452        ˇbbb
 7453        ccc
 7454
 7455        bbb
 7456        ccc
 7457        "});
 7458    cx.dispatch_action(SelectPrevious::default());
 7459    cx.assert_editor_state(indoc! {"
 7460                «bbbˇ»
 7461                ccc
 7462
 7463                bbb
 7464                ccc
 7465                "});
 7466    cx.dispatch_action(SelectPrevious::default());
 7467    cx.assert_editor_state(indoc! {"
 7468                «bbbˇ»
 7469                ccc
 7470
 7471                «bbbˇ»
 7472                ccc
 7473                "});
 7474}
 7475
 7476#[gpui::test]
 7477async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 7478    init_test(cx, |_| {});
 7479
 7480    let mut cx = EditorTestContext::new(cx).await;
 7481    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7482
 7483    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7484        .unwrap();
 7485    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7486
 7487    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7488        .unwrap();
 7489    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 7490
 7491    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7492    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7493
 7494    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7495    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 7496
 7497    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7498        .unwrap();
 7499    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 7500
 7501    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7502        .unwrap();
 7503    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7504}
 7505
 7506#[gpui::test]
 7507async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 7508    init_test(cx, |_| {});
 7509
 7510    let mut cx = EditorTestContext::new(cx).await;
 7511    cx.set_state("");
 7512
 7513    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7514        .unwrap();
 7515    cx.assert_editor_state("«aˇ»");
 7516    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7517        .unwrap();
 7518    cx.assert_editor_state("«aˇ»");
 7519}
 7520
 7521#[gpui::test]
 7522async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 7523    init_test(cx, |_| {});
 7524
 7525    let mut cx = EditorTestContext::new(cx).await;
 7526    cx.set_state(
 7527        r#"let foo = 2;
 7528lˇet foo = 2;
 7529let fooˇ = 2;
 7530let foo = 2;
 7531let foo = ˇ2;"#,
 7532    );
 7533
 7534    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7535        .unwrap();
 7536    cx.assert_editor_state(
 7537        r#"let foo = 2;
 7538«letˇ» foo = 2;
 7539let «fooˇ» = 2;
 7540let foo = 2;
 7541let foo = «2ˇ»;"#,
 7542    );
 7543
 7544    // noop for multiple selections with different contents
 7545    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7546        .unwrap();
 7547    cx.assert_editor_state(
 7548        r#"let foo = 2;
 7549«letˇ» foo = 2;
 7550let «fooˇ» = 2;
 7551let foo = 2;
 7552let foo = «2ˇ»;"#,
 7553    );
 7554}
 7555
 7556#[gpui::test]
 7557async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 7558    init_test(cx, |_| {});
 7559
 7560    let mut cx = EditorTestContext::new(cx).await;
 7561    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 7562
 7563    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7564        .unwrap();
 7565    // selection direction is preserved
 7566    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7567
 7568    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7569        .unwrap();
 7570    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7571
 7572    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7573    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7574
 7575    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7576    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7577
 7578    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7579        .unwrap();
 7580    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 7581
 7582    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7583        .unwrap();
 7584    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 7585}
 7586
 7587#[gpui::test]
 7588async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 7589    init_test(cx, |_| {});
 7590
 7591    let language = Arc::new(Language::new(
 7592        LanguageConfig::default(),
 7593        Some(tree_sitter_rust::LANGUAGE.into()),
 7594    ));
 7595
 7596    let text = r#"
 7597        use mod1::mod2::{mod3, mod4};
 7598
 7599        fn fn_1(param1: bool, param2: &str) {
 7600            let var1 = "text";
 7601        }
 7602    "#
 7603    .unindent();
 7604
 7605    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7606    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7607    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7608
 7609    editor
 7610        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7611        .await;
 7612
 7613    editor.update_in(cx, |editor, window, cx| {
 7614        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7615            s.select_display_ranges([
 7616                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 7617                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 7618                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 7619            ]);
 7620        });
 7621        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7622    });
 7623    editor.update(cx, |editor, cx| {
 7624        assert_text_with_selections(
 7625            editor,
 7626            indoc! {r#"
 7627                use mod1::mod2::{mod3, «mod4ˇ»};
 7628
 7629                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7630                    let var1 = "«ˇtext»";
 7631                }
 7632            "#},
 7633            cx,
 7634        );
 7635    });
 7636
 7637    editor.update_in(cx, |editor, window, cx| {
 7638        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7639    });
 7640    editor.update(cx, |editor, cx| {
 7641        assert_text_with_selections(
 7642            editor,
 7643            indoc! {r#"
 7644                use mod1::mod2::«{mod3, mod4}ˇ»;
 7645
 7646                «ˇfn fn_1(param1: bool, param2: &str) {
 7647                    let var1 = "text";
 7648 7649            "#},
 7650            cx,
 7651        );
 7652    });
 7653
 7654    editor.update_in(cx, |editor, window, cx| {
 7655        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7656    });
 7657    assert_eq!(
 7658        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7659        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7660    );
 7661
 7662    // Trying to expand the selected syntax node one more time has no effect.
 7663    editor.update_in(cx, |editor, window, cx| {
 7664        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7665    });
 7666    assert_eq!(
 7667        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7668        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7669    );
 7670
 7671    editor.update_in(cx, |editor, window, cx| {
 7672        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7673    });
 7674    editor.update(cx, |editor, cx| {
 7675        assert_text_with_selections(
 7676            editor,
 7677            indoc! {r#"
 7678                use mod1::mod2::«{mod3, mod4}ˇ»;
 7679
 7680                «ˇfn fn_1(param1: bool, param2: &str) {
 7681                    let var1 = "text";
 7682 7683            "#},
 7684            cx,
 7685        );
 7686    });
 7687
 7688    editor.update_in(cx, |editor, window, cx| {
 7689        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7690    });
 7691    editor.update(cx, |editor, cx| {
 7692        assert_text_with_selections(
 7693            editor,
 7694            indoc! {r#"
 7695                use mod1::mod2::{mod3, «mod4ˇ»};
 7696
 7697                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7698                    let var1 = "«ˇtext»";
 7699                }
 7700            "#},
 7701            cx,
 7702        );
 7703    });
 7704
 7705    editor.update_in(cx, |editor, window, cx| {
 7706        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7707    });
 7708    editor.update(cx, |editor, cx| {
 7709        assert_text_with_selections(
 7710            editor,
 7711            indoc! {r#"
 7712                use mod1::mod2::{mod3, mo«ˇ»d4};
 7713
 7714                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7715                    let var1 = "te«ˇ»xt";
 7716                }
 7717            "#},
 7718            cx,
 7719        );
 7720    });
 7721
 7722    // Trying to shrink the selected syntax node one more time has no effect.
 7723    editor.update_in(cx, |editor, window, cx| {
 7724        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7725    });
 7726    editor.update_in(cx, |editor, _, cx| {
 7727        assert_text_with_selections(
 7728            editor,
 7729            indoc! {r#"
 7730                use mod1::mod2::{mod3, mo«ˇ»d4};
 7731
 7732                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7733                    let var1 = "te«ˇ»xt";
 7734                }
 7735            "#},
 7736            cx,
 7737        );
 7738    });
 7739
 7740    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 7741    // a fold.
 7742    editor.update_in(cx, |editor, window, cx| {
 7743        editor.fold_creases(
 7744            vec![
 7745                Crease::simple(
 7746                    Point::new(0, 21)..Point::new(0, 24),
 7747                    FoldPlaceholder::test(),
 7748                ),
 7749                Crease::simple(
 7750                    Point::new(3, 20)..Point::new(3, 22),
 7751                    FoldPlaceholder::test(),
 7752                ),
 7753            ],
 7754            true,
 7755            window,
 7756            cx,
 7757        );
 7758        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7759    });
 7760    editor.update(cx, |editor, cx| {
 7761        assert_text_with_selections(
 7762            editor,
 7763            indoc! {r#"
 7764                use mod1::mod2::«{mod3, mod4}ˇ»;
 7765
 7766                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7767                    let var1 = "«ˇtext»";
 7768                }
 7769            "#},
 7770            cx,
 7771        );
 7772    });
 7773}
 7774
 7775#[gpui::test]
 7776async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 7777    init_test(cx, |_| {});
 7778
 7779    let language = Arc::new(Language::new(
 7780        LanguageConfig::default(),
 7781        Some(tree_sitter_rust::LANGUAGE.into()),
 7782    ));
 7783
 7784    let text = "let a = 2;";
 7785
 7786    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7787    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7788    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7789
 7790    editor
 7791        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7792        .await;
 7793
 7794    // Test case 1: Cursor at end of word
 7795    editor.update_in(cx, |editor, window, cx| {
 7796        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7797            s.select_display_ranges([
 7798                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 7799            ]);
 7800        });
 7801    });
 7802    editor.update(cx, |editor, cx| {
 7803        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 7804    });
 7805    editor.update_in(cx, |editor, window, cx| {
 7806        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7807    });
 7808    editor.update(cx, |editor, cx| {
 7809        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 7810    });
 7811    editor.update_in(cx, |editor, window, cx| {
 7812        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7813    });
 7814    editor.update(cx, |editor, cx| {
 7815        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7816    });
 7817
 7818    // Test case 2: Cursor at end of statement
 7819    editor.update_in(cx, |editor, window, cx| {
 7820        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7821            s.select_display_ranges([
 7822                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 7823            ]);
 7824        });
 7825    });
 7826    editor.update(cx, |editor, cx| {
 7827        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 7828    });
 7829    editor.update_in(cx, |editor, window, cx| {
 7830        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7831    });
 7832    editor.update(cx, |editor, cx| {
 7833        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7834    });
 7835}
 7836
 7837#[gpui::test]
 7838async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 7839    init_test(cx, |_| {});
 7840
 7841    let language = Arc::new(Language::new(
 7842        LanguageConfig::default(),
 7843        Some(tree_sitter_rust::LANGUAGE.into()),
 7844    ));
 7845
 7846    let text = r#"
 7847        use mod1::mod2::{mod3, mod4};
 7848
 7849        fn fn_1(param1: bool, param2: &str) {
 7850            let var1 = "hello world";
 7851        }
 7852    "#
 7853    .unindent();
 7854
 7855    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7856    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7857    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7858
 7859    editor
 7860        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7861        .await;
 7862
 7863    // Test 1: Cursor on a letter of a string word
 7864    editor.update_in(cx, |editor, window, cx| {
 7865        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7866            s.select_display_ranges([
 7867                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 7868            ]);
 7869        });
 7870    });
 7871    editor.update_in(cx, |editor, window, cx| {
 7872        assert_text_with_selections(
 7873            editor,
 7874            indoc! {r#"
 7875                use mod1::mod2::{mod3, mod4};
 7876
 7877                fn fn_1(param1: bool, param2: &str) {
 7878                    let var1 = "hˇello world";
 7879                }
 7880            "#},
 7881            cx,
 7882        );
 7883        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7884        assert_text_with_selections(
 7885            editor,
 7886            indoc! {r#"
 7887                use mod1::mod2::{mod3, mod4};
 7888
 7889                fn fn_1(param1: bool, param2: &str) {
 7890                    let var1 = "«ˇhello» world";
 7891                }
 7892            "#},
 7893            cx,
 7894        );
 7895    });
 7896
 7897    // Test 2: Partial selection within a word
 7898    editor.update_in(cx, |editor, window, cx| {
 7899        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7900            s.select_display_ranges([
 7901                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 7902            ]);
 7903        });
 7904    });
 7905    editor.update_in(cx, |editor, window, cx| {
 7906        assert_text_with_selections(
 7907            editor,
 7908            indoc! {r#"
 7909                use mod1::mod2::{mod3, mod4};
 7910
 7911                fn fn_1(param1: bool, param2: &str) {
 7912                    let var1 = "h«elˇ»lo world";
 7913                }
 7914            "#},
 7915            cx,
 7916        );
 7917        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7918        assert_text_with_selections(
 7919            editor,
 7920            indoc! {r#"
 7921                use mod1::mod2::{mod3, mod4};
 7922
 7923                fn fn_1(param1: bool, param2: &str) {
 7924                    let var1 = "«ˇhello» world";
 7925                }
 7926            "#},
 7927            cx,
 7928        );
 7929    });
 7930
 7931    // Test 3: Complete word already selected
 7932    editor.update_in(cx, |editor, window, cx| {
 7933        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7934            s.select_display_ranges([
 7935                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 7936            ]);
 7937        });
 7938    });
 7939    editor.update_in(cx, |editor, window, cx| {
 7940        assert_text_with_selections(
 7941            editor,
 7942            indoc! {r#"
 7943                use mod1::mod2::{mod3, mod4};
 7944
 7945                fn fn_1(param1: bool, param2: &str) {
 7946                    let var1 = "«helloˇ» world";
 7947                }
 7948            "#},
 7949            cx,
 7950        );
 7951        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7952        assert_text_with_selections(
 7953            editor,
 7954            indoc! {r#"
 7955                use mod1::mod2::{mod3, mod4};
 7956
 7957                fn fn_1(param1: bool, param2: &str) {
 7958                    let var1 = "«hello worldˇ»";
 7959                }
 7960            "#},
 7961            cx,
 7962        );
 7963    });
 7964
 7965    // Test 4: Selection spanning across words
 7966    editor.update_in(cx, |editor, window, cx| {
 7967        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7968            s.select_display_ranges([
 7969                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 7970            ]);
 7971        });
 7972    });
 7973    editor.update_in(cx, |editor, window, cx| {
 7974        assert_text_with_selections(
 7975            editor,
 7976            indoc! {r#"
 7977                use mod1::mod2::{mod3, mod4};
 7978
 7979                fn fn_1(param1: bool, param2: &str) {
 7980                    let var1 = "hel«lo woˇ»rld";
 7981                }
 7982            "#},
 7983            cx,
 7984        );
 7985        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7986        assert_text_with_selections(
 7987            editor,
 7988            indoc! {r#"
 7989                use mod1::mod2::{mod3, mod4};
 7990
 7991                fn fn_1(param1: bool, param2: &str) {
 7992                    let var1 = "«ˇhello world»";
 7993                }
 7994            "#},
 7995            cx,
 7996        );
 7997    });
 7998
 7999    // Test 5: Expansion beyond string
 8000    editor.update_in(cx, |editor, window, cx| {
 8001        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8002        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8003        assert_text_with_selections(
 8004            editor,
 8005            indoc! {r#"
 8006                use mod1::mod2::{mod3, mod4};
 8007
 8008                fn fn_1(param1: bool, param2: &str) {
 8009                    «ˇlet var1 = "hello world";»
 8010                }
 8011            "#},
 8012            cx,
 8013        );
 8014    });
 8015}
 8016
 8017#[gpui::test]
 8018async fn test_unwrap_syntax_node(cx: &mut gpui::TestAppContext) {
 8019    init_test(cx, |_| {});
 8020
 8021    let mut cx = EditorTestContext::new(cx).await;
 8022
 8023    let language = Arc::new(Language::new(
 8024        LanguageConfig::default(),
 8025        Some(tree_sitter_rust::LANGUAGE.into()),
 8026    ));
 8027
 8028    cx.update_buffer(|buffer, cx| {
 8029        buffer.set_language(Some(language), cx);
 8030    });
 8031
 8032    cx.set_state(
 8033        &r#"
 8034            use mod1::mod2::{«mod3ˇ», mod4};
 8035        "#
 8036        .unindent(),
 8037    );
 8038    cx.update_editor(|editor, window, cx| {
 8039        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 8040    });
 8041    cx.assert_editor_state(
 8042        &r#"
 8043            use mod1::mod2::«mod3ˇ»;
 8044        "#
 8045        .unindent(),
 8046    );
 8047}
 8048
 8049#[gpui::test]
 8050async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 8051    init_test(cx, |_| {});
 8052
 8053    let base_text = r#"
 8054        impl A {
 8055            // this is an uncommitted comment
 8056
 8057            fn b() {
 8058                c();
 8059            }
 8060
 8061            // this is another uncommitted comment
 8062
 8063            fn d() {
 8064                // e
 8065                // f
 8066            }
 8067        }
 8068
 8069        fn g() {
 8070            // h
 8071        }
 8072    "#
 8073    .unindent();
 8074
 8075    let text = r#"
 8076        ˇimpl A {
 8077
 8078            fn b() {
 8079                c();
 8080            }
 8081
 8082            fn d() {
 8083                // e
 8084                // f
 8085            }
 8086        }
 8087
 8088        fn g() {
 8089            // h
 8090        }
 8091    "#
 8092    .unindent();
 8093
 8094    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 8095    cx.set_state(&text);
 8096    cx.set_head_text(&base_text);
 8097    cx.update_editor(|editor, window, cx| {
 8098        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 8099    });
 8100
 8101    cx.assert_state_with_diff(
 8102        "
 8103        ˇimpl A {
 8104      -     // this is an uncommitted comment
 8105
 8106            fn b() {
 8107                c();
 8108            }
 8109
 8110      -     // this is another uncommitted comment
 8111      -
 8112            fn d() {
 8113                // e
 8114                // f
 8115            }
 8116        }
 8117
 8118        fn g() {
 8119            // h
 8120        }
 8121    "
 8122        .unindent(),
 8123    );
 8124
 8125    let expected_display_text = "
 8126        impl A {
 8127            // this is an uncommitted comment
 8128
 8129            fn b() {
 8130 8131            }
 8132
 8133            // this is another uncommitted comment
 8134
 8135            fn d() {
 8136 8137            }
 8138        }
 8139
 8140        fn g() {
 8141 8142        }
 8143        "
 8144    .unindent();
 8145
 8146    cx.update_editor(|editor, window, cx| {
 8147        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 8148        assert_eq!(editor.display_text(cx), expected_display_text);
 8149    });
 8150}
 8151
 8152#[gpui::test]
 8153async fn test_autoindent(cx: &mut TestAppContext) {
 8154    init_test(cx, |_| {});
 8155
 8156    let language = Arc::new(
 8157        Language::new(
 8158            LanguageConfig {
 8159                brackets: BracketPairConfig {
 8160                    pairs: vec![
 8161                        BracketPair {
 8162                            start: "{".to_string(),
 8163                            end: "}".to_string(),
 8164                            close: false,
 8165                            surround: false,
 8166                            newline: true,
 8167                        },
 8168                        BracketPair {
 8169                            start: "(".to_string(),
 8170                            end: ")".to_string(),
 8171                            close: false,
 8172                            surround: false,
 8173                            newline: true,
 8174                        },
 8175                    ],
 8176                    ..Default::default()
 8177                },
 8178                ..Default::default()
 8179            },
 8180            Some(tree_sitter_rust::LANGUAGE.into()),
 8181        )
 8182        .with_indents_query(
 8183            r#"
 8184                (_ "(" ")" @end) @indent
 8185                (_ "{" "}" @end) @indent
 8186            "#,
 8187        )
 8188        .unwrap(),
 8189    );
 8190
 8191    let text = "fn a() {}";
 8192
 8193    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8194    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8195    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8196    editor
 8197        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8198        .await;
 8199
 8200    editor.update_in(cx, |editor, window, cx| {
 8201        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8202            s.select_ranges([5..5, 8..8, 9..9])
 8203        });
 8204        editor.newline(&Newline, window, cx);
 8205        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 8206        assert_eq!(
 8207            editor.selections.ranges(cx),
 8208            &[
 8209                Point::new(1, 4)..Point::new(1, 4),
 8210                Point::new(3, 4)..Point::new(3, 4),
 8211                Point::new(5, 0)..Point::new(5, 0)
 8212            ]
 8213        );
 8214    });
 8215}
 8216
 8217#[gpui::test]
 8218async fn test_autoindent_disabled(cx: &mut TestAppContext) {
 8219    init_test(cx, |settings| settings.defaults.auto_indent = Some(false));
 8220
 8221    let language = Arc::new(
 8222        Language::new(
 8223            LanguageConfig {
 8224                brackets: BracketPairConfig {
 8225                    pairs: vec![
 8226                        BracketPair {
 8227                            start: "{".to_string(),
 8228                            end: "}".to_string(),
 8229                            close: false,
 8230                            surround: false,
 8231                            newline: true,
 8232                        },
 8233                        BracketPair {
 8234                            start: "(".to_string(),
 8235                            end: ")".to_string(),
 8236                            close: false,
 8237                            surround: false,
 8238                            newline: true,
 8239                        },
 8240                    ],
 8241                    ..Default::default()
 8242                },
 8243                ..Default::default()
 8244            },
 8245            Some(tree_sitter_rust::LANGUAGE.into()),
 8246        )
 8247        .with_indents_query(
 8248            r#"
 8249                (_ "(" ")" @end) @indent
 8250                (_ "{" "}" @end) @indent
 8251            "#,
 8252        )
 8253        .unwrap(),
 8254    );
 8255
 8256    let text = "fn a() {}";
 8257
 8258    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8259    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8260    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8261    editor
 8262        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8263        .await;
 8264
 8265    editor.update_in(cx, |editor, window, cx| {
 8266        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8267            s.select_ranges([5..5, 8..8, 9..9])
 8268        });
 8269        editor.newline(&Newline, window, cx);
 8270        assert_eq!(
 8271            editor.text(cx),
 8272            indoc!(
 8273                "
 8274                fn a(
 8275
 8276                ) {
 8277
 8278                }
 8279                "
 8280            )
 8281        );
 8282        assert_eq!(
 8283            editor.selections.ranges(cx),
 8284            &[
 8285                Point::new(1, 0)..Point::new(1, 0),
 8286                Point::new(3, 0)..Point::new(3, 0),
 8287                Point::new(5, 0)..Point::new(5, 0)
 8288            ]
 8289        );
 8290    });
 8291}
 8292
 8293#[gpui::test]
 8294async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
 8295    init_test(cx, |settings| {
 8296        settings.defaults.auto_indent = Some(true);
 8297        settings.languages.0.insert(
 8298            "python".into(),
 8299            LanguageSettingsContent {
 8300                auto_indent: Some(false),
 8301                ..Default::default()
 8302            },
 8303        );
 8304    });
 8305
 8306    let mut cx = EditorTestContext::new(cx).await;
 8307
 8308    let injected_language = Arc::new(
 8309        Language::new(
 8310            LanguageConfig {
 8311                brackets: BracketPairConfig {
 8312                    pairs: vec![
 8313                        BracketPair {
 8314                            start: "{".to_string(),
 8315                            end: "}".to_string(),
 8316                            close: false,
 8317                            surround: false,
 8318                            newline: true,
 8319                        },
 8320                        BracketPair {
 8321                            start: "(".to_string(),
 8322                            end: ")".to_string(),
 8323                            close: true,
 8324                            surround: false,
 8325                            newline: true,
 8326                        },
 8327                    ],
 8328                    ..Default::default()
 8329                },
 8330                name: "python".into(),
 8331                ..Default::default()
 8332            },
 8333            Some(tree_sitter_python::LANGUAGE.into()),
 8334        )
 8335        .with_indents_query(
 8336            r#"
 8337                (_ "(" ")" @end) @indent
 8338                (_ "{" "}" @end) @indent
 8339            "#,
 8340        )
 8341        .unwrap(),
 8342    );
 8343
 8344    let language = Arc::new(
 8345        Language::new(
 8346            LanguageConfig {
 8347                brackets: BracketPairConfig {
 8348                    pairs: vec![
 8349                        BracketPair {
 8350                            start: "{".to_string(),
 8351                            end: "}".to_string(),
 8352                            close: false,
 8353                            surround: false,
 8354                            newline: true,
 8355                        },
 8356                        BracketPair {
 8357                            start: "(".to_string(),
 8358                            end: ")".to_string(),
 8359                            close: true,
 8360                            surround: false,
 8361                            newline: true,
 8362                        },
 8363                    ],
 8364                    ..Default::default()
 8365                },
 8366                name: LanguageName::new("rust"),
 8367                ..Default::default()
 8368            },
 8369            Some(tree_sitter_rust::LANGUAGE.into()),
 8370        )
 8371        .with_indents_query(
 8372            r#"
 8373                (_ "(" ")" @end) @indent
 8374                (_ "{" "}" @end) @indent
 8375            "#,
 8376        )
 8377        .unwrap()
 8378        .with_injection_query(
 8379            r#"
 8380            (macro_invocation
 8381                macro: (identifier) @_macro_name
 8382                (token_tree) @injection.content
 8383                (#set! injection.language "python"))
 8384           "#,
 8385        )
 8386        .unwrap(),
 8387    );
 8388
 8389    cx.language_registry().add(injected_language);
 8390    cx.language_registry().add(language.clone());
 8391
 8392    cx.update_buffer(|buffer, cx| {
 8393        buffer.set_language(Some(language), cx);
 8394    });
 8395
 8396    cx.set_state(r#"struct A {ˇ}"#);
 8397
 8398    cx.update_editor(|editor, window, cx| {
 8399        editor.newline(&Default::default(), window, cx);
 8400    });
 8401
 8402    cx.assert_editor_state(indoc!(
 8403        "struct A {
 8404            ˇ
 8405        }"
 8406    ));
 8407
 8408    cx.set_state(r#"select_biased!(ˇ)"#);
 8409
 8410    cx.update_editor(|editor, window, cx| {
 8411        editor.newline(&Default::default(), window, cx);
 8412        editor.handle_input("def ", window, cx);
 8413        editor.handle_input("(", window, cx);
 8414        editor.newline(&Default::default(), window, cx);
 8415        editor.handle_input("a", window, cx);
 8416    });
 8417
 8418    cx.assert_editor_state(indoc!(
 8419        "select_biased!(
 8420        def (
 8421 8422        )
 8423        )"
 8424    ));
 8425}
 8426
 8427#[gpui::test]
 8428async fn test_autoindent_selections(cx: &mut TestAppContext) {
 8429    init_test(cx, |_| {});
 8430
 8431    {
 8432        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 8433        cx.set_state(indoc! {"
 8434            impl A {
 8435
 8436                fn b() {}
 8437
 8438            «fn c() {
 8439
 8440            }ˇ»
 8441            }
 8442        "});
 8443
 8444        cx.update_editor(|editor, window, cx| {
 8445            editor.autoindent(&Default::default(), window, cx);
 8446        });
 8447
 8448        cx.assert_editor_state(indoc! {"
 8449            impl A {
 8450
 8451                fn b() {}
 8452
 8453                «fn c() {
 8454
 8455                }ˇ»
 8456            }
 8457        "});
 8458    }
 8459
 8460    {
 8461        let mut cx = EditorTestContext::new_multibuffer(
 8462            cx,
 8463            [indoc! { "
 8464                impl A {
 8465                «
 8466                // a
 8467                fn b(){}
 8468                »
 8469                «
 8470                    }
 8471                    fn c(){}
 8472                »
 8473            "}],
 8474        );
 8475
 8476        let buffer = cx.update_editor(|editor, _, cx| {
 8477            let buffer = editor.buffer().update(cx, |buffer, _| {
 8478                buffer.all_buffers().iter().next().unwrap().clone()
 8479            });
 8480            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 8481            buffer
 8482        });
 8483
 8484        cx.run_until_parked();
 8485        cx.update_editor(|editor, window, cx| {
 8486            editor.select_all(&Default::default(), window, cx);
 8487            editor.autoindent(&Default::default(), window, cx)
 8488        });
 8489        cx.run_until_parked();
 8490
 8491        cx.update(|_, cx| {
 8492            assert_eq!(
 8493                buffer.read(cx).text(),
 8494                indoc! { "
 8495                    impl A {
 8496
 8497                        // a
 8498                        fn b(){}
 8499
 8500
 8501                    }
 8502                    fn c(){}
 8503
 8504                " }
 8505            )
 8506        });
 8507    }
 8508}
 8509
 8510#[gpui::test]
 8511async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 8512    init_test(cx, |_| {});
 8513
 8514    let mut cx = EditorTestContext::new(cx).await;
 8515
 8516    let language = Arc::new(Language::new(
 8517        LanguageConfig {
 8518            brackets: BracketPairConfig {
 8519                pairs: vec![
 8520                    BracketPair {
 8521                        start: "{".to_string(),
 8522                        end: "}".to_string(),
 8523                        close: true,
 8524                        surround: true,
 8525                        newline: true,
 8526                    },
 8527                    BracketPair {
 8528                        start: "(".to_string(),
 8529                        end: ")".to_string(),
 8530                        close: true,
 8531                        surround: true,
 8532                        newline: true,
 8533                    },
 8534                    BracketPair {
 8535                        start: "/*".to_string(),
 8536                        end: " */".to_string(),
 8537                        close: true,
 8538                        surround: true,
 8539                        newline: true,
 8540                    },
 8541                    BracketPair {
 8542                        start: "[".to_string(),
 8543                        end: "]".to_string(),
 8544                        close: false,
 8545                        surround: false,
 8546                        newline: true,
 8547                    },
 8548                    BracketPair {
 8549                        start: "\"".to_string(),
 8550                        end: "\"".to_string(),
 8551                        close: true,
 8552                        surround: true,
 8553                        newline: false,
 8554                    },
 8555                    BracketPair {
 8556                        start: "<".to_string(),
 8557                        end: ">".to_string(),
 8558                        close: false,
 8559                        surround: true,
 8560                        newline: true,
 8561                    },
 8562                ],
 8563                ..Default::default()
 8564            },
 8565            autoclose_before: "})]".to_string(),
 8566            ..Default::default()
 8567        },
 8568        Some(tree_sitter_rust::LANGUAGE.into()),
 8569    ));
 8570
 8571    cx.language_registry().add(language.clone());
 8572    cx.update_buffer(|buffer, cx| {
 8573        buffer.set_language(Some(language), cx);
 8574    });
 8575
 8576    cx.set_state(
 8577        &r#"
 8578            🏀ˇ
 8579            εˇ
 8580            ❤️ˇ
 8581        "#
 8582        .unindent(),
 8583    );
 8584
 8585    // autoclose multiple nested brackets at multiple cursors
 8586    cx.update_editor(|editor, window, cx| {
 8587        editor.handle_input("{", window, cx);
 8588        editor.handle_input("{", window, cx);
 8589        editor.handle_input("{", window, cx);
 8590    });
 8591    cx.assert_editor_state(
 8592        &"
 8593            🏀{{{ˇ}}}
 8594            ε{{{ˇ}}}
 8595            ❤️{{{ˇ}}}
 8596        "
 8597        .unindent(),
 8598    );
 8599
 8600    // insert a different closing bracket
 8601    cx.update_editor(|editor, window, cx| {
 8602        editor.handle_input(")", window, cx);
 8603    });
 8604    cx.assert_editor_state(
 8605        &"
 8606            🏀{{{)ˇ}}}
 8607            ε{{{)ˇ}}}
 8608            ❤️{{{)ˇ}}}
 8609        "
 8610        .unindent(),
 8611    );
 8612
 8613    // skip over the auto-closed brackets when typing a closing bracket
 8614    cx.update_editor(|editor, window, cx| {
 8615        editor.move_right(&MoveRight, window, cx);
 8616        editor.handle_input("}", window, cx);
 8617        editor.handle_input("}", window, cx);
 8618        editor.handle_input("}", window, cx);
 8619    });
 8620    cx.assert_editor_state(
 8621        &"
 8622            🏀{{{)}}}}ˇ
 8623            ε{{{)}}}}ˇ
 8624            ❤️{{{)}}}}ˇ
 8625        "
 8626        .unindent(),
 8627    );
 8628
 8629    // autoclose multi-character pairs
 8630    cx.set_state(
 8631        &"
 8632            ˇ
 8633            ˇ
 8634        "
 8635        .unindent(),
 8636    );
 8637    cx.update_editor(|editor, window, cx| {
 8638        editor.handle_input("/", window, cx);
 8639        editor.handle_input("*", window, cx);
 8640    });
 8641    cx.assert_editor_state(
 8642        &"
 8643            /*ˇ */
 8644            /*ˇ */
 8645        "
 8646        .unindent(),
 8647    );
 8648
 8649    // one cursor autocloses a multi-character pair, one cursor
 8650    // does not autoclose.
 8651    cx.set_state(
 8652        &"
 8653 8654            ˇ
 8655        "
 8656        .unindent(),
 8657    );
 8658    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 8659    cx.assert_editor_state(
 8660        &"
 8661            /*ˇ */
 8662 8663        "
 8664        .unindent(),
 8665    );
 8666
 8667    // Don't autoclose if the next character isn't whitespace and isn't
 8668    // listed in the language's "autoclose_before" section.
 8669    cx.set_state("ˇa b");
 8670    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8671    cx.assert_editor_state("{ˇa b");
 8672
 8673    // Don't autoclose if `close` is false for the bracket pair
 8674    cx.set_state("ˇ");
 8675    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 8676    cx.assert_editor_state("");
 8677
 8678    // Surround with brackets if text is selected
 8679    cx.set_state("«aˇ» b");
 8680    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8681    cx.assert_editor_state("{«aˇ»} b");
 8682
 8683    // Autoclose when not immediately after a word character
 8684    cx.set_state("a ˇ");
 8685    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8686    cx.assert_editor_state("a \"ˇ\"");
 8687
 8688    // Autoclose pair where the start and end characters are the same
 8689    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8690    cx.assert_editor_state("a \"\"ˇ");
 8691
 8692    // Don't autoclose when immediately after a word character
 8693    cx.set_state("");
 8694    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8695    cx.assert_editor_state("a\"ˇ");
 8696
 8697    // Do autoclose when after a non-word character
 8698    cx.set_state("");
 8699    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8700    cx.assert_editor_state("{\"ˇ\"");
 8701
 8702    // Non identical pairs autoclose regardless of preceding character
 8703    cx.set_state("");
 8704    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8705    cx.assert_editor_state("a{ˇ}");
 8706
 8707    // Don't autoclose pair if autoclose is disabled
 8708    cx.set_state("ˇ");
 8709    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 8710    cx.assert_editor_state("");
 8711
 8712    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 8713    cx.set_state("«aˇ» b");
 8714    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 8715    cx.assert_editor_state("<«aˇ»> b");
 8716}
 8717
 8718#[gpui::test]
 8719async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 8720    init_test(cx, |settings| {
 8721        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 8722    });
 8723
 8724    let mut cx = EditorTestContext::new(cx).await;
 8725
 8726    let language = Arc::new(Language::new(
 8727        LanguageConfig {
 8728            brackets: BracketPairConfig {
 8729                pairs: vec![
 8730                    BracketPair {
 8731                        start: "{".to_string(),
 8732                        end: "}".to_string(),
 8733                        close: true,
 8734                        surround: true,
 8735                        newline: true,
 8736                    },
 8737                    BracketPair {
 8738                        start: "(".to_string(),
 8739                        end: ")".to_string(),
 8740                        close: true,
 8741                        surround: true,
 8742                        newline: true,
 8743                    },
 8744                    BracketPair {
 8745                        start: "[".to_string(),
 8746                        end: "]".to_string(),
 8747                        close: false,
 8748                        surround: false,
 8749                        newline: true,
 8750                    },
 8751                ],
 8752                ..Default::default()
 8753            },
 8754            autoclose_before: "})]".to_string(),
 8755            ..Default::default()
 8756        },
 8757        Some(tree_sitter_rust::LANGUAGE.into()),
 8758    ));
 8759
 8760    cx.language_registry().add(language.clone());
 8761    cx.update_buffer(|buffer, cx| {
 8762        buffer.set_language(Some(language), cx);
 8763    });
 8764
 8765    cx.set_state(
 8766        &"
 8767            ˇ
 8768            ˇ
 8769            ˇ
 8770        "
 8771        .unindent(),
 8772    );
 8773
 8774    // ensure only matching closing brackets are skipped over
 8775    cx.update_editor(|editor, window, cx| {
 8776        editor.handle_input("}", window, cx);
 8777        editor.move_left(&MoveLeft, window, cx);
 8778        editor.handle_input(")", window, cx);
 8779        editor.move_left(&MoveLeft, window, cx);
 8780    });
 8781    cx.assert_editor_state(
 8782        &"
 8783            ˇ)}
 8784            ˇ)}
 8785            ˇ)}
 8786        "
 8787        .unindent(),
 8788    );
 8789
 8790    // skip-over closing brackets at multiple cursors
 8791    cx.update_editor(|editor, window, cx| {
 8792        editor.handle_input(")", window, cx);
 8793        editor.handle_input("}", window, cx);
 8794    });
 8795    cx.assert_editor_state(
 8796        &"
 8797            )}ˇ
 8798            )}ˇ
 8799            )}ˇ
 8800        "
 8801        .unindent(),
 8802    );
 8803
 8804    // ignore non-close brackets
 8805    cx.update_editor(|editor, window, cx| {
 8806        editor.handle_input("]", window, cx);
 8807        editor.move_left(&MoveLeft, window, cx);
 8808        editor.handle_input("]", window, cx);
 8809    });
 8810    cx.assert_editor_state(
 8811        &"
 8812            )}]ˇ]
 8813            )}]ˇ]
 8814            )}]ˇ]
 8815        "
 8816        .unindent(),
 8817    );
 8818}
 8819
 8820#[gpui::test]
 8821async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 8822    init_test(cx, |_| {});
 8823
 8824    let mut cx = EditorTestContext::new(cx).await;
 8825
 8826    let html_language = Arc::new(
 8827        Language::new(
 8828            LanguageConfig {
 8829                name: "HTML".into(),
 8830                brackets: BracketPairConfig {
 8831                    pairs: vec![
 8832                        BracketPair {
 8833                            start: "<".into(),
 8834                            end: ">".into(),
 8835                            close: true,
 8836                            ..Default::default()
 8837                        },
 8838                        BracketPair {
 8839                            start: "{".into(),
 8840                            end: "}".into(),
 8841                            close: true,
 8842                            ..Default::default()
 8843                        },
 8844                        BracketPair {
 8845                            start: "(".into(),
 8846                            end: ")".into(),
 8847                            close: true,
 8848                            ..Default::default()
 8849                        },
 8850                    ],
 8851                    ..Default::default()
 8852                },
 8853                autoclose_before: "})]>".into(),
 8854                ..Default::default()
 8855            },
 8856            Some(tree_sitter_html::LANGUAGE.into()),
 8857        )
 8858        .with_injection_query(
 8859            r#"
 8860            (script_element
 8861                (raw_text) @injection.content
 8862                (#set! injection.language "javascript"))
 8863            "#,
 8864        )
 8865        .unwrap(),
 8866    );
 8867
 8868    let javascript_language = Arc::new(Language::new(
 8869        LanguageConfig {
 8870            name: "JavaScript".into(),
 8871            brackets: BracketPairConfig {
 8872                pairs: vec![
 8873                    BracketPair {
 8874                        start: "/*".into(),
 8875                        end: " */".into(),
 8876                        close: true,
 8877                        ..Default::default()
 8878                    },
 8879                    BracketPair {
 8880                        start: "{".into(),
 8881                        end: "}".into(),
 8882                        close: true,
 8883                        ..Default::default()
 8884                    },
 8885                    BracketPair {
 8886                        start: "(".into(),
 8887                        end: ")".into(),
 8888                        close: true,
 8889                        ..Default::default()
 8890                    },
 8891                ],
 8892                ..Default::default()
 8893            },
 8894            autoclose_before: "})]>".into(),
 8895            ..Default::default()
 8896        },
 8897        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 8898    ));
 8899
 8900    cx.language_registry().add(html_language.clone());
 8901    cx.language_registry().add(javascript_language.clone());
 8902    cx.executor().run_until_parked();
 8903
 8904    cx.update_buffer(|buffer, cx| {
 8905        buffer.set_language(Some(html_language), cx);
 8906    });
 8907
 8908    cx.set_state(
 8909        &r#"
 8910            <body>ˇ
 8911                <script>
 8912                    var x = 1;ˇ
 8913                </script>
 8914            </body>ˇ
 8915        "#
 8916        .unindent(),
 8917    );
 8918
 8919    // Precondition: different languages are active at different locations.
 8920    cx.update_editor(|editor, window, cx| {
 8921        let snapshot = editor.snapshot(window, cx);
 8922        let cursors = editor.selections.ranges::<usize>(cx);
 8923        let languages = cursors
 8924            .iter()
 8925            .map(|c| snapshot.language_at(c.start).unwrap().name())
 8926            .collect::<Vec<_>>();
 8927        assert_eq!(
 8928            languages,
 8929            &["HTML".into(), "JavaScript".into(), "HTML".into()]
 8930        );
 8931    });
 8932
 8933    // Angle brackets autoclose in HTML, but not JavaScript.
 8934    cx.update_editor(|editor, window, cx| {
 8935        editor.handle_input("<", window, cx);
 8936        editor.handle_input("a", window, cx);
 8937    });
 8938    cx.assert_editor_state(
 8939        &r#"
 8940            <body><aˇ>
 8941                <script>
 8942                    var x = 1;<aˇ
 8943                </script>
 8944            </body><aˇ>
 8945        "#
 8946        .unindent(),
 8947    );
 8948
 8949    // Curly braces and parens autoclose in both HTML and JavaScript.
 8950    cx.update_editor(|editor, window, cx| {
 8951        editor.handle_input(" b=", window, cx);
 8952        editor.handle_input("{", window, cx);
 8953        editor.handle_input("c", window, cx);
 8954        editor.handle_input("(", window, cx);
 8955    });
 8956    cx.assert_editor_state(
 8957        &r#"
 8958            <body><a b={c(ˇ)}>
 8959                <script>
 8960                    var x = 1;<a b={c(ˇ)}
 8961                </script>
 8962            </body><a b={c(ˇ)}>
 8963        "#
 8964        .unindent(),
 8965    );
 8966
 8967    // Brackets that were already autoclosed are skipped.
 8968    cx.update_editor(|editor, window, cx| {
 8969        editor.handle_input(")", window, cx);
 8970        editor.handle_input("d", window, cx);
 8971        editor.handle_input("}", window, cx);
 8972    });
 8973    cx.assert_editor_state(
 8974        &r#"
 8975            <body><a b={c()d}ˇ>
 8976                <script>
 8977                    var x = 1;<a b={c()d}ˇ
 8978                </script>
 8979            </body><a b={c()d}ˇ>
 8980        "#
 8981        .unindent(),
 8982    );
 8983    cx.update_editor(|editor, window, cx| {
 8984        editor.handle_input(">", window, cx);
 8985    });
 8986    cx.assert_editor_state(
 8987        &r#"
 8988            <body><a b={c()d}>ˇ
 8989                <script>
 8990                    var x = 1;<a b={c()d}>ˇ
 8991                </script>
 8992            </body><a b={c()d}>ˇ
 8993        "#
 8994        .unindent(),
 8995    );
 8996
 8997    // Reset
 8998    cx.set_state(
 8999        &r#"
 9000            <body>ˇ
 9001                <script>
 9002                    var x = 1;ˇ
 9003                </script>
 9004            </body>ˇ
 9005        "#
 9006        .unindent(),
 9007    );
 9008
 9009    cx.update_editor(|editor, window, cx| {
 9010        editor.handle_input("<", window, cx);
 9011    });
 9012    cx.assert_editor_state(
 9013        &r#"
 9014            <body><ˇ>
 9015                <script>
 9016                    var x = 1;<ˇ
 9017                </script>
 9018            </body><ˇ>
 9019        "#
 9020        .unindent(),
 9021    );
 9022
 9023    // When backspacing, the closing angle brackets are removed.
 9024    cx.update_editor(|editor, window, cx| {
 9025        editor.backspace(&Backspace, window, cx);
 9026    });
 9027    cx.assert_editor_state(
 9028        &r#"
 9029            <body>ˇ
 9030                <script>
 9031                    var x = 1;ˇ
 9032                </script>
 9033            </body>ˇ
 9034        "#
 9035        .unindent(),
 9036    );
 9037
 9038    // Block comments autoclose in JavaScript, but not HTML.
 9039    cx.update_editor(|editor, window, cx| {
 9040        editor.handle_input("/", window, cx);
 9041        editor.handle_input("*", window, cx);
 9042    });
 9043    cx.assert_editor_state(
 9044        &r#"
 9045            <body>/*ˇ
 9046                <script>
 9047                    var x = 1;/*ˇ */
 9048                </script>
 9049            </body>/*ˇ
 9050        "#
 9051        .unindent(),
 9052    );
 9053}
 9054
 9055#[gpui::test]
 9056async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
 9057    init_test(cx, |_| {});
 9058
 9059    let mut cx = EditorTestContext::new(cx).await;
 9060
 9061    let rust_language = Arc::new(
 9062        Language::new(
 9063            LanguageConfig {
 9064                name: "Rust".into(),
 9065                brackets: serde_json::from_value(json!([
 9066                    { "start": "{", "end": "}", "close": true, "newline": true },
 9067                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
 9068                ]))
 9069                .unwrap(),
 9070                autoclose_before: "})]>".into(),
 9071                ..Default::default()
 9072            },
 9073            Some(tree_sitter_rust::LANGUAGE.into()),
 9074        )
 9075        .with_override_query("(string_literal) @string")
 9076        .unwrap(),
 9077    );
 9078
 9079    cx.language_registry().add(rust_language.clone());
 9080    cx.update_buffer(|buffer, cx| {
 9081        buffer.set_language(Some(rust_language), cx);
 9082    });
 9083
 9084    cx.set_state(
 9085        &r#"
 9086            let x = ˇ
 9087        "#
 9088        .unindent(),
 9089    );
 9090
 9091    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
 9092    cx.update_editor(|editor, window, cx| {
 9093        editor.handle_input("\"", window, cx);
 9094    });
 9095    cx.assert_editor_state(
 9096        &r#"
 9097            let x = "ˇ"
 9098        "#
 9099        .unindent(),
 9100    );
 9101
 9102    // Inserting another quotation mark. The cursor moves across the existing
 9103    // automatically-inserted quotation mark.
 9104    cx.update_editor(|editor, window, cx| {
 9105        editor.handle_input("\"", window, cx);
 9106    });
 9107    cx.assert_editor_state(
 9108        &r#"
 9109            let x = ""ˇ
 9110        "#
 9111        .unindent(),
 9112    );
 9113
 9114    // Reset
 9115    cx.set_state(
 9116        &r#"
 9117            let x = ˇ
 9118        "#
 9119        .unindent(),
 9120    );
 9121
 9122    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
 9123    cx.update_editor(|editor, window, cx| {
 9124        editor.handle_input("\"", window, cx);
 9125        editor.handle_input(" ", window, cx);
 9126        editor.move_left(&Default::default(), window, cx);
 9127        editor.handle_input("\\", window, cx);
 9128        editor.handle_input("\"", window, cx);
 9129    });
 9130    cx.assert_editor_state(
 9131        &r#"
 9132            let x = "\"ˇ "
 9133        "#
 9134        .unindent(),
 9135    );
 9136
 9137    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
 9138    // mark. Nothing is inserted.
 9139    cx.update_editor(|editor, window, cx| {
 9140        editor.move_right(&Default::default(), window, cx);
 9141        editor.handle_input("\"", window, cx);
 9142    });
 9143    cx.assert_editor_state(
 9144        &r#"
 9145            let x = "\" "ˇ
 9146        "#
 9147        .unindent(),
 9148    );
 9149}
 9150
 9151#[gpui::test]
 9152async fn test_surround_with_pair(cx: &mut TestAppContext) {
 9153    init_test(cx, |_| {});
 9154
 9155    let language = Arc::new(Language::new(
 9156        LanguageConfig {
 9157            brackets: BracketPairConfig {
 9158                pairs: vec![
 9159                    BracketPair {
 9160                        start: "{".to_string(),
 9161                        end: "}".to_string(),
 9162                        close: true,
 9163                        surround: true,
 9164                        newline: true,
 9165                    },
 9166                    BracketPair {
 9167                        start: "/* ".to_string(),
 9168                        end: "*/".to_string(),
 9169                        close: true,
 9170                        surround: true,
 9171                        ..Default::default()
 9172                    },
 9173                ],
 9174                ..Default::default()
 9175            },
 9176            ..Default::default()
 9177        },
 9178        Some(tree_sitter_rust::LANGUAGE.into()),
 9179    ));
 9180
 9181    let text = r#"
 9182        a
 9183        b
 9184        c
 9185    "#
 9186    .unindent();
 9187
 9188    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9189    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9190    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9191    editor
 9192        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9193        .await;
 9194
 9195    editor.update_in(cx, |editor, window, cx| {
 9196        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9197            s.select_display_ranges([
 9198                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 9199                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 9200                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
 9201            ])
 9202        });
 9203
 9204        editor.handle_input("{", window, cx);
 9205        editor.handle_input("{", window, cx);
 9206        editor.handle_input("{", window, cx);
 9207        assert_eq!(
 9208            editor.text(cx),
 9209            "
 9210                {{{a}}}
 9211                {{{b}}}
 9212                {{{c}}}
 9213            "
 9214            .unindent()
 9215        );
 9216        assert_eq!(
 9217            editor.selections.display_ranges(cx),
 9218            [
 9219                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
 9220                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
 9221                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
 9222            ]
 9223        );
 9224
 9225        editor.undo(&Undo, window, cx);
 9226        editor.undo(&Undo, window, cx);
 9227        editor.undo(&Undo, window, cx);
 9228        assert_eq!(
 9229            editor.text(cx),
 9230            "
 9231                a
 9232                b
 9233                c
 9234            "
 9235            .unindent()
 9236        );
 9237        assert_eq!(
 9238            editor.selections.display_ranges(cx),
 9239            [
 9240                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 9241                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 9242                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 9243            ]
 9244        );
 9245
 9246        // Ensure inserting the first character of a multi-byte bracket pair
 9247        // doesn't surround the selections with the bracket.
 9248        editor.handle_input("/", window, cx);
 9249        assert_eq!(
 9250            editor.text(cx),
 9251            "
 9252                /
 9253                /
 9254                /
 9255            "
 9256            .unindent()
 9257        );
 9258        assert_eq!(
 9259            editor.selections.display_ranges(cx),
 9260            [
 9261                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 9262                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 9263                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 9264            ]
 9265        );
 9266
 9267        editor.undo(&Undo, window, cx);
 9268        assert_eq!(
 9269            editor.text(cx),
 9270            "
 9271                a
 9272                b
 9273                c
 9274            "
 9275            .unindent()
 9276        );
 9277        assert_eq!(
 9278            editor.selections.display_ranges(cx),
 9279            [
 9280                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 9281                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 9282                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 9283            ]
 9284        );
 9285
 9286        // Ensure inserting the last character of a multi-byte bracket pair
 9287        // doesn't surround the selections with the bracket.
 9288        editor.handle_input("*", window, cx);
 9289        assert_eq!(
 9290            editor.text(cx),
 9291            "
 9292                *
 9293                *
 9294                *
 9295            "
 9296            .unindent()
 9297        );
 9298        assert_eq!(
 9299            editor.selections.display_ranges(cx),
 9300            [
 9301                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 9302                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 9303                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 9304            ]
 9305        );
 9306    });
 9307}
 9308
 9309#[gpui::test]
 9310async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
 9311    init_test(cx, |_| {});
 9312
 9313    let language = Arc::new(Language::new(
 9314        LanguageConfig {
 9315            brackets: BracketPairConfig {
 9316                pairs: vec![BracketPair {
 9317                    start: "{".to_string(),
 9318                    end: "}".to_string(),
 9319                    close: true,
 9320                    surround: true,
 9321                    newline: true,
 9322                }],
 9323                ..Default::default()
 9324            },
 9325            autoclose_before: "}".to_string(),
 9326            ..Default::default()
 9327        },
 9328        Some(tree_sitter_rust::LANGUAGE.into()),
 9329    ));
 9330
 9331    let text = r#"
 9332        a
 9333        b
 9334        c
 9335    "#
 9336    .unindent();
 9337
 9338    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9339    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9340    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9341    editor
 9342        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9343        .await;
 9344
 9345    editor.update_in(cx, |editor, window, cx| {
 9346        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9347            s.select_ranges([
 9348                Point::new(0, 1)..Point::new(0, 1),
 9349                Point::new(1, 1)..Point::new(1, 1),
 9350                Point::new(2, 1)..Point::new(2, 1),
 9351            ])
 9352        });
 9353
 9354        editor.handle_input("{", window, cx);
 9355        editor.handle_input("{", window, cx);
 9356        editor.handle_input("_", window, cx);
 9357        assert_eq!(
 9358            editor.text(cx),
 9359            "
 9360                a{{_}}
 9361                b{{_}}
 9362                c{{_}}
 9363            "
 9364            .unindent()
 9365        );
 9366        assert_eq!(
 9367            editor.selections.ranges::<Point>(cx),
 9368            [
 9369                Point::new(0, 4)..Point::new(0, 4),
 9370                Point::new(1, 4)..Point::new(1, 4),
 9371                Point::new(2, 4)..Point::new(2, 4)
 9372            ]
 9373        );
 9374
 9375        editor.backspace(&Default::default(), window, cx);
 9376        editor.backspace(&Default::default(), window, cx);
 9377        assert_eq!(
 9378            editor.text(cx),
 9379            "
 9380                a{}
 9381                b{}
 9382                c{}
 9383            "
 9384            .unindent()
 9385        );
 9386        assert_eq!(
 9387            editor.selections.ranges::<Point>(cx),
 9388            [
 9389                Point::new(0, 2)..Point::new(0, 2),
 9390                Point::new(1, 2)..Point::new(1, 2),
 9391                Point::new(2, 2)..Point::new(2, 2)
 9392            ]
 9393        );
 9394
 9395        editor.delete_to_previous_word_start(&Default::default(), window, cx);
 9396        assert_eq!(
 9397            editor.text(cx),
 9398            "
 9399                a
 9400                b
 9401                c
 9402            "
 9403            .unindent()
 9404        );
 9405        assert_eq!(
 9406            editor.selections.ranges::<Point>(cx),
 9407            [
 9408                Point::new(0, 1)..Point::new(0, 1),
 9409                Point::new(1, 1)..Point::new(1, 1),
 9410                Point::new(2, 1)..Point::new(2, 1)
 9411            ]
 9412        );
 9413    });
 9414}
 9415
 9416#[gpui::test]
 9417async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
 9418    init_test(cx, |settings| {
 9419        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 9420    });
 9421
 9422    let mut cx = EditorTestContext::new(cx).await;
 9423
 9424    let language = Arc::new(Language::new(
 9425        LanguageConfig {
 9426            brackets: BracketPairConfig {
 9427                pairs: vec![
 9428                    BracketPair {
 9429                        start: "{".to_string(),
 9430                        end: "}".to_string(),
 9431                        close: true,
 9432                        surround: true,
 9433                        newline: true,
 9434                    },
 9435                    BracketPair {
 9436                        start: "(".to_string(),
 9437                        end: ")".to_string(),
 9438                        close: true,
 9439                        surround: true,
 9440                        newline: true,
 9441                    },
 9442                    BracketPair {
 9443                        start: "[".to_string(),
 9444                        end: "]".to_string(),
 9445                        close: false,
 9446                        surround: true,
 9447                        newline: true,
 9448                    },
 9449                ],
 9450                ..Default::default()
 9451            },
 9452            autoclose_before: "})]".to_string(),
 9453            ..Default::default()
 9454        },
 9455        Some(tree_sitter_rust::LANGUAGE.into()),
 9456    ));
 9457
 9458    cx.language_registry().add(language.clone());
 9459    cx.update_buffer(|buffer, cx| {
 9460        buffer.set_language(Some(language), cx);
 9461    });
 9462
 9463    cx.set_state(
 9464        &"
 9465            {(ˇ)}
 9466            [[ˇ]]
 9467            {(ˇ)}
 9468        "
 9469        .unindent(),
 9470    );
 9471
 9472    cx.update_editor(|editor, window, cx| {
 9473        editor.backspace(&Default::default(), window, cx);
 9474        editor.backspace(&Default::default(), window, cx);
 9475    });
 9476
 9477    cx.assert_editor_state(
 9478        &"
 9479            ˇ
 9480            ˇ]]
 9481            ˇ
 9482        "
 9483        .unindent(),
 9484    );
 9485
 9486    cx.update_editor(|editor, window, cx| {
 9487        editor.handle_input("{", window, cx);
 9488        editor.handle_input("{", window, cx);
 9489        editor.move_right(&MoveRight, window, cx);
 9490        editor.move_right(&MoveRight, window, cx);
 9491        editor.move_left(&MoveLeft, window, cx);
 9492        editor.move_left(&MoveLeft, window, cx);
 9493        editor.backspace(&Default::default(), window, cx);
 9494    });
 9495
 9496    cx.assert_editor_state(
 9497        &"
 9498            {ˇ}
 9499            {ˇ}]]
 9500            {ˇ}
 9501        "
 9502        .unindent(),
 9503    );
 9504
 9505    cx.update_editor(|editor, window, cx| {
 9506        editor.backspace(&Default::default(), window, cx);
 9507    });
 9508
 9509    cx.assert_editor_state(
 9510        &"
 9511            ˇ
 9512            ˇ]]
 9513            ˇ
 9514        "
 9515        .unindent(),
 9516    );
 9517}
 9518
 9519#[gpui::test]
 9520async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
 9521    init_test(cx, |_| {});
 9522
 9523    let language = Arc::new(Language::new(
 9524        LanguageConfig::default(),
 9525        Some(tree_sitter_rust::LANGUAGE.into()),
 9526    ));
 9527
 9528    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
 9529    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9530    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9531    editor
 9532        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9533        .await;
 9534
 9535    editor.update_in(cx, |editor, window, cx| {
 9536        editor.set_auto_replace_emoji_shortcode(true);
 9537
 9538        editor.handle_input("Hello ", window, cx);
 9539        editor.handle_input(":wave", window, cx);
 9540        assert_eq!(editor.text(cx), "Hello :wave".unindent());
 9541
 9542        editor.handle_input(":", window, cx);
 9543        assert_eq!(editor.text(cx), "Hello 👋".unindent());
 9544
 9545        editor.handle_input(" :smile", window, cx);
 9546        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
 9547
 9548        editor.handle_input(":", window, cx);
 9549        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
 9550
 9551        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
 9552        editor.handle_input(":wave", window, cx);
 9553        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
 9554
 9555        editor.handle_input(":", window, cx);
 9556        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
 9557
 9558        editor.handle_input(":1", window, cx);
 9559        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
 9560
 9561        editor.handle_input(":", window, cx);
 9562        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
 9563
 9564        // Ensure shortcode does not get replaced when it is part of a word
 9565        editor.handle_input(" Test:wave", window, cx);
 9566        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
 9567
 9568        editor.handle_input(":", window, cx);
 9569        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
 9570
 9571        editor.set_auto_replace_emoji_shortcode(false);
 9572
 9573        // Ensure shortcode does not get replaced when auto replace is off
 9574        editor.handle_input(" :wave", window, cx);
 9575        assert_eq!(
 9576            editor.text(cx),
 9577            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
 9578        );
 9579
 9580        editor.handle_input(":", window, cx);
 9581        assert_eq!(
 9582            editor.text(cx),
 9583            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
 9584        );
 9585    });
 9586}
 9587
 9588#[gpui::test]
 9589async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
 9590    init_test(cx, |_| {});
 9591
 9592    let (text, insertion_ranges) = marked_text_ranges(
 9593        indoc! {"
 9594            ˇ
 9595        "},
 9596        false,
 9597    );
 9598
 9599    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
 9600    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9601
 9602    _ = editor.update_in(cx, |editor, window, cx| {
 9603        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
 9604
 9605        editor
 9606            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9607            .unwrap();
 9608
 9609        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
 9610            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
 9611            assert_eq!(editor.text(cx), expected_text);
 9612            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
 9613        }
 9614
 9615        assert(
 9616            editor,
 9617            cx,
 9618            indoc! {"
 9619            type «» =•
 9620            "},
 9621        );
 9622
 9623        assert!(editor.context_menu_visible(), "There should be a matches");
 9624    });
 9625}
 9626
 9627#[gpui::test]
 9628async fn test_snippets(cx: &mut TestAppContext) {
 9629    init_test(cx, |_| {});
 9630
 9631    let mut cx = EditorTestContext::new(cx).await;
 9632
 9633    cx.set_state(indoc! {"
 9634        a.ˇ b
 9635        a.ˇ b
 9636        a.ˇ b
 9637    "});
 9638
 9639    cx.update_editor(|editor, window, cx| {
 9640        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
 9641        let insertion_ranges = editor
 9642            .selections
 9643            .all(cx)
 9644            .iter()
 9645            .map(|s| s.range().clone())
 9646            .collect::<Vec<_>>();
 9647        editor
 9648            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9649            .unwrap();
 9650    });
 9651
 9652    cx.assert_editor_state(indoc! {"
 9653        a.f(«oneˇ», two, «threeˇ») b
 9654        a.f(«oneˇ», two, «threeˇ») b
 9655        a.f(«oneˇ», two, «threeˇ») b
 9656    "});
 9657
 9658    // Can't move earlier than the first tab stop
 9659    cx.update_editor(|editor, window, cx| {
 9660        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 9661    });
 9662    cx.assert_editor_state(indoc! {"
 9663        a.f(«oneˇ», two, «threeˇ») b
 9664        a.f(«oneˇ», two, «threeˇ») b
 9665        a.f(«oneˇ», two, «threeˇ») b
 9666    "});
 9667
 9668    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9669    cx.assert_editor_state(indoc! {"
 9670        a.f(one, «twoˇ», three) b
 9671        a.f(one, «twoˇ», three) b
 9672        a.f(one, «twoˇ», three) b
 9673    "});
 9674
 9675    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
 9676    cx.assert_editor_state(indoc! {"
 9677        a.f(«oneˇ», two, «threeˇ») b
 9678        a.f(«oneˇ», two, «threeˇ») b
 9679        a.f(«oneˇ», two, «threeˇ») b
 9680    "});
 9681
 9682    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9683    cx.assert_editor_state(indoc! {"
 9684        a.f(one, «twoˇ», three) b
 9685        a.f(one, «twoˇ», three) b
 9686        a.f(one, «twoˇ», three) b
 9687    "});
 9688    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9689    cx.assert_editor_state(indoc! {"
 9690        a.f(one, two, three)ˇ b
 9691        a.f(one, two, three)ˇ b
 9692        a.f(one, two, three)ˇ b
 9693    "});
 9694
 9695    // As soon as the last tab stop is reached, snippet state is gone
 9696    cx.update_editor(|editor, window, cx| {
 9697        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 9698    });
 9699    cx.assert_editor_state(indoc! {"
 9700        a.f(one, two, three)ˇ b
 9701        a.f(one, two, three)ˇ b
 9702        a.f(one, two, three)ˇ b
 9703    "});
 9704}
 9705
 9706#[gpui::test]
 9707async fn test_snippet_indentation(cx: &mut TestAppContext) {
 9708    init_test(cx, |_| {});
 9709
 9710    let mut cx = EditorTestContext::new(cx).await;
 9711
 9712    cx.update_editor(|editor, window, cx| {
 9713        let snippet = Snippet::parse(indoc! {"
 9714            /*
 9715             * Multiline comment with leading indentation
 9716             *
 9717             * $1
 9718             */
 9719            $0"})
 9720        .unwrap();
 9721        let insertion_ranges = editor
 9722            .selections
 9723            .all(cx)
 9724            .iter()
 9725            .map(|s| s.range().clone())
 9726            .collect::<Vec<_>>();
 9727        editor
 9728            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9729            .unwrap();
 9730    });
 9731
 9732    cx.assert_editor_state(indoc! {"
 9733        /*
 9734         * Multiline comment with leading indentation
 9735         *
 9736         * ˇ
 9737         */
 9738    "});
 9739
 9740    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9741    cx.assert_editor_state(indoc! {"
 9742        /*
 9743         * Multiline comment with leading indentation
 9744         *
 9745         *•
 9746         */
 9747        ˇ"});
 9748}
 9749
 9750#[gpui::test]
 9751async fn test_document_format_during_save(cx: &mut TestAppContext) {
 9752    init_test(cx, |_| {});
 9753
 9754    let fs = FakeFs::new(cx.executor());
 9755    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9756
 9757    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
 9758
 9759    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9760    language_registry.add(rust_lang());
 9761    let mut fake_servers = language_registry.register_fake_lsp(
 9762        "Rust",
 9763        FakeLspAdapter {
 9764            capabilities: lsp::ServerCapabilities {
 9765                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9766                ..Default::default()
 9767            },
 9768            ..Default::default()
 9769        },
 9770    );
 9771
 9772    let buffer = project
 9773        .update(cx, |project, cx| {
 9774            project.open_local_buffer(path!("/file.rs"), cx)
 9775        })
 9776        .await
 9777        .unwrap();
 9778
 9779    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9780    let (editor, cx) = cx.add_window_view(|window, cx| {
 9781        build_editor_with_project(project.clone(), buffer, window, cx)
 9782    });
 9783    editor.update_in(cx, |editor, window, cx| {
 9784        editor.set_text("one\ntwo\nthree\n", window, cx)
 9785    });
 9786    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9787
 9788    cx.executor().start_waiting();
 9789    let fake_server = fake_servers.next().await.unwrap();
 9790
 9791    {
 9792        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9793            move |params, _| async move {
 9794                assert_eq!(
 9795                    params.text_document.uri,
 9796                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9797                );
 9798                assert_eq!(params.options.tab_size, 4);
 9799                Ok(Some(vec![lsp::TextEdit::new(
 9800                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9801                    ", ".to_string(),
 9802                )]))
 9803            },
 9804        );
 9805        let save = editor
 9806            .update_in(cx, |editor, window, cx| {
 9807                editor.save(
 9808                    SaveOptions {
 9809                        format: true,
 9810                        autosave: false,
 9811                    },
 9812                    project.clone(),
 9813                    window,
 9814                    cx,
 9815                )
 9816            })
 9817            .unwrap();
 9818        cx.executor().start_waiting();
 9819        save.await;
 9820
 9821        assert_eq!(
 9822            editor.update(cx, |editor, cx| editor.text(cx)),
 9823            "one, two\nthree\n"
 9824        );
 9825        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9826    }
 9827
 9828    {
 9829        editor.update_in(cx, |editor, window, cx| {
 9830            editor.set_text("one\ntwo\nthree\n", window, cx)
 9831        });
 9832        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9833
 9834        // Ensure we can still save even if formatting hangs.
 9835        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9836            move |params, _| async move {
 9837                assert_eq!(
 9838                    params.text_document.uri,
 9839                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9840                );
 9841                futures::future::pending::<()>().await;
 9842                unreachable!()
 9843            },
 9844        );
 9845        let save = editor
 9846            .update_in(cx, |editor, window, cx| {
 9847                editor.save(
 9848                    SaveOptions {
 9849                        format: true,
 9850                        autosave: false,
 9851                    },
 9852                    project.clone(),
 9853                    window,
 9854                    cx,
 9855                )
 9856            })
 9857            .unwrap();
 9858        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9859        cx.executor().start_waiting();
 9860        save.await;
 9861        assert_eq!(
 9862            editor.update(cx, |editor, cx| editor.text(cx)),
 9863            "one\ntwo\nthree\n"
 9864        );
 9865    }
 9866
 9867    // Set rust language override and assert overridden tabsize is sent to language server
 9868    update_test_language_settings(cx, |settings| {
 9869        settings.languages.0.insert(
 9870            "Rust".into(),
 9871            LanguageSettingsContent {
 9872                tab_size: NonZeroU32::new(8),
 9873                ..Default::default()
 9874            },
 9875        );
 9876    });
 9877
 9878    {
 9879        editor.update_in(cx, |editor, window, cx| {
 9880            editor.set_text("somehting_new\n", window, cx)
 9881        });
 9882        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9883        let _formatting_request_signal = fake_server
 9884            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9885                assert_eq!(
 9886                    params.text_document.uri,
 9887                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9888                );
 9889                assert_eq!(params.options.tab_size, 8);
 9890                Ok(Some(vec![]))
 9891            });
 9892        let save = editor
 9893            .update_in(cx, |editor, window, cx| {
 9894                editor.save(
 9895                    SaveOptions {
 9896                        format: true,
 9897                        autosave: false,
 9898                    },
 9899                    project.clone(),
 9900                    window,
 9901                    cx,
 9902                )
 9903            })
 9904            .unwrap();
 9905        cx.executor().start_waiting();
 9906        save.await;
 9907    }
 9908}
 9909
 9910#[gpui::test]
 9911async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
 9912    init_test(cx, |settings| {
 9913        settings.defaults.ensure_final_newline_on_save = Some(false);
 9914    });
 9915
 9916    let fs = FakeFs::new(cx.executor());
 9917    fs.insert_file(path!("/file.txt"), "foo".into()).await;
 9918
 9919    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
 9920
 9921    let buffer = project
 9922        .update(cx, |project, cx| {
 9923            project.open_local_buffer(path!("/file.txt"), cx)
 9924        })
 9925        .await
 9926        .unwrap();
 9927
 9928    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9929    let (editor, cx) = cx.add_window_view(|window, cx| {
 9930        build_editor_with_project(project.clone(), buffer, window, cx)
 9931    });
 9932    editor.update_in(cx, |editor, window, cx| {
 9933        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 9934            s.select_ranges([0..0])
 9935        });
 9936    });
 9937    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9938
 9939    editor.update_in(cx, |editor, window, cx| {
 9940        editor.handle_input("\n", window, cx)
 9941    });
 9942    cx.run_until_parked();
 9943    save(&editor, &project, cx).await;
 9944    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
 9945
 9946    editor.update_in(cx, |editor, window, cx| {
 9947        editor.undo(&Default::default(), window, cx);
 9948    });
 9949    save(&editor, &project, cx).await;
 9950    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
 9951
 9952    editor.update_in(cx, |editor, window, cx| {
 9953        editor.redo(&Default::default(), window, cx);
 9954    });
 9955    cx.run_until_parked();
 9956    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
 9957
 9958    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
 9959        let save = editor
 9960            .update_in(cx, |editor, window, cx| {
 9961                editor.save(
 9962                    SaveOptions {
 9963                        format: true,
 9964                        autosave: false,
 9965                    },
 9966                    project.clone(),
 9967                    window,
 9968                    cx,
 9969                )
 9970            })
 9971            .unwrap();
 9972        cx.executor().start_waiting();
 9973        save.await;
 9974        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9975    }
 9976}
 9977
 9978#[gpui::test]
 9979async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
 9980    init_test(cx, |_| {});
 9981
 9982    let cols = 4;
 9983    let rows = 10;
 9984    let sample_text_1 = sample_text(rows, cols, 'a');
 9985    assert_eq!(
 9986        sample_text_1,
 9987        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
 9988    );
 9989    let sample_text_2 = sample_text(rows, cols, 'l');
 9990    assert_eq!(
 9991        sample_text_2,
 9992        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
 9993    );
 9994    let sample_text_3 = sample_text(rows, cols, 'v');
 9995    assert_eq!(
 9996        sample_text_3,
 9997        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
 9998    );
 9999
10000    let fs = FakeFs::new(cx.executor());
10001    fs.insert_tree(
10002        path!("/a"),
10003        json!({
10004            "main.rs": sample_text_1,
10005            "other.rs": sample_text_2,
10006            "lib.rs": sample_text_3,
10007        }),
10008    )
10009    .await;
10010
10011    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
10012    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
10013    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10014
10015    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10016    language_registry.add(rust_lang());
10017    let mut fake_servers = language_registry.register_fake_lsp(
10018        "Rust",
10019        FakeLspAdapter {
10020            capabilities: lsp::ServerCapabilities {
10021                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10022                ..Default::default()
10023            },
10024            ..Default::default()
10025        },
10026    );
10027
10028    let worktree = project.update(cx, |project, cx| {
10029        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
10030        assert_eq!(worktrees.len(), 1);
10031        worktrees.pop().unwrap()
10032    });
10033    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
10034
10035    let buffer_1 = project
10036        .update(cx, |project, cx| {
10037            project.open_buffer((worktree_id, "main.rs"), cx)
10038        })
10039        .await
10040        .unwrap();
10041    let buffer_2 = project
10042        .update(cx, |project, cx| {
10043            project.open_buffer((worktree_id, "other.rs"), cx)
10044        })
10045        .await
10046        .unwrap();
10047    let buffer_3 = project
10048        .update(cx, |project, cx| {
10049            project.open_buffer((worktree_id, "lib.rs"), cx)
10050        })
10051        .await
10052        .unwrap();
10053
10054    let multi_buffer = cx.new(|cx| {
10055        let mut multi_buffer = MultiBuffer::new(ReadWrite);
10056        multi_buffer.push_excerpts(
10057            buffer_1.clone(),
10058            [
10059                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
10060                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
10061                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
10062            ],
10063            cx,
10064        );
10065        multi_buffer.push_excerpts(
10066            buffer_2.clone(),
10067            [
10068                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
10069                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
10070                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
10071            ],
10072            cx,
10073        );
10074        multi_buffer.push_excerpts(
10075            buffer_3.clone(),
10076            [
10077                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
10078                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
10079                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
10080            ],
10081            cx,
10082        );
10083        multi_buffer
10084    });
10085    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
10086        Editor::new(
10087            EditorMode::full(),
10088            multi_buffer,
10089            Some(project.clone()),
10090            window,
10091            cx,
10092        )
10093    });
10094
10095    multi_buffer_editor.update_in(cx, |editor, window, cx| {
10096        editor.change_selections(
10097            SelectionEffects::scroll(Autoscroll::Next),
10098            window,
10099            cx,
10100            |s| s.select_ranges(Some(1..2)),
10101        );
10102        editor.insert("|one|two|three|", window, cx);
10103    });
10104    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
10105    multi_buffer_editor.update_in(cx, |editor, window, cx| {
10106        editor.change_selections(
10107            SelectionEffects::scroll(Autoscroll::Next),
10108            window,
10109            cx,
10110            |s| s.select_ranges(Some(60..70)),
10111        );
10112        editor.insert("|four|five|six|", window, cx);
10113    });
10114    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
10115
10116    // First two buffers should be edited, but not the third one.
10117    assert_eq!(
10118        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
10119        "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}",
10120    );
10121    buffer_1.update(cx, |buffer, _| {
10122        assert!(buffer.is_dirty());
10123        assert_eq!(
10124            buffer.text(),
10125            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
10126        )
10127    });
10128    buffer_2.update(cx, |buffer, _| {
10129        assert!(buffer.is_dirty());
10130        assert_eq!(
10131            buffer.text(),
10132            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
10133        )
10134    });
10135    buffer_3.update(cx, |buffer, _| {
10136        assert!(!buffer.is_dirty());
10137        assert_eq!(buffer.text(), sample_text_3,)
10138    });
10139    cx.executor().run_until_parked();
10140
10141    cx.executor().start_waiting();
10142    let save = multi_buffer_editor
10143        .update_in(cx, |editor, window, cx| {
10144            editor.save(
10145                SaveOptions {
10146                    format: true,
10147                    autosave: false,
10148                },
10149                project.clone(),
10150                window,
10151                cx,
10152            )
10153        })
10154        .unwrap();
10155
10156    let fake_server = fake_servers.next().await.unwrap();
10157    fake_server
10158        .server
10159        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
10160            Ok(Some(vec![lsp::TextEdit::new(
10161                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10162                format!("[{} formatted]", params.text_document.uri),
10163            )]))
10164        })
10165        .detach();
10166    save.await;
10167
10168    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
10169    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
10170    assert_eq!(
10171        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
10172        uri!(
10173            "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}"
10174        ),
10175    );
10176    buffer_1.update(cx, |buffer, _| {
10177        assert!(!buffer.is_dirty());
10178        assert_eq!(
10179            buffer.text(),
10180            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
10181        )
10182    });
10183    buffer_2.update(cx, |buffer, _| {
10184        assert!(!buffer.is_dirty());
10185        assert_eq!(
10186            buffer.text(),
10187            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
10188        )
10189    });
10190    buffer_3.update(cx, |buffer, _| {
10191        assert!(!buffer.is_dirty());
10192        assert_eq!(buffer.text(), sample_text_3,)
10193    });
10194}
10195
10196#[gpui::test]
10197async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
10198    init_test(cx, |_| {});
10199
10200    let fs = FakeFs::new(cx.executor());
10201    fs.insert_tree(
10202        path!("/dir"),
10203        json!({
10204            "file1.rs": "fn main() { println!(\"hello\"); }",
10205            "file2.rs": "fn test() { println!(\"test\"); }",
10206            "file3.rs": "fn other() { println!(\"other\"); }\n",
10207        }),
10208    )
10209    .await;
10210
10211    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
10212    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
10213    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10214
10215    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10216    language_registry.add(rust_lang());
10217
10218    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
10219    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
10220
10221    // Open three buffers
10222    let buffer_1 = project
10223        .update(cx, |project, cx| {
10224            project.open_buffer((worktree_id, "file1.rs"), cx)
10225        })
10226        .await
10227        .unwrap();
10228    let buffer_2 = project
10229        .update(cx, |project, cx| {
10230            project.open_buffer((worktree_id, "file2.rs"), cx)
10231        })
10232        .await
10233        .unwrap();
10234    let buffer_3 = project
10235        .update(cx, |project, cx| {
10236            project.open_buffer((worktree_id, "file3.rs"), cx)
10237        })
10238        .await
10239        .unwrap();
10240
10241    // Create a multi-buffer with all three buffers
10242    let multi_buffer = cx.new(|cx| {
10243        let mut multi_buffer = MultiBuffer::new(ReadWrite);
10244        multi_buffer.push_excerpts(
10245            buffer_1.clone(),
10246            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
10247            cx,
10248        );
10249        multi_buffer.push_excerpts(
10250            buffer_2.clone(),
10251            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
10252            cx,
10253        );
10254        multi_buffer.push_excerpts(
10255            buffer_3.clone(),
10256            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
10257            cx,
10258        );
10259        multi_buffer
10260    });
10261
10262    let editor = cx.new_window_entity(|window, cx| {
10263        Editor::new(
10264            EditorMode::full(),
10265            multi_buffer,
10266            Some(project.clone()),
10267            window,
10268            cx,
10269        )
10270    });
10271
10272    // Edit only the first buffer
10273    editor.update_in(cx, |editor, window, cx| {
10274        editor.change_selections(
10275            SelectionEffects::scroll(Autoscroll::Next),
10276            window,
10277            cx,
10278            |s| s.select_ranges(Some(10..10)),
10279        );
10280        editor.insert("// edited", window, cx);
10281    });
10282
10283    // Verify that only buffer 1 is dirty
10284    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
10285    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10286    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10287
10288    // Get write counts after file creation (files were created with initial content)
10289    // We expect each file to have been written once during creation
10290    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
10291    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
10292    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
10293
10294    // Perform autosave
10295    let save_task = editor.update_in(cx, |editor, window, cx| {
10296        editor.save(
10297            SaveOptions {
10298                format: true,
10299                autosave: true,
10300            },
10301            project.clone(),
10302            window,
10303            cx,
10304        )
10305    });
10306    save_task.await.unwrap();
10307
10308    // Only the dirty buffer should have been saved
10309    assert_eq!(
10310        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
10311        1,
10312        "Buffer 1 was dirty, so it should have been written once during autosave"
10313    );
10314    assert_eq!(
10315        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
10316        0,
10317        "Buffer 2 was clean, so it should not have been written during autosave"
10318    );
10319    assert_eq!(
10320        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
10321        0,
10322        "Buffer 3 was clean, so it should not have been written during autosave"
10323    );
10324
10325    // Verify buffer states after autosave
10326    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10327    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10328    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10329
10330    // Now perform a manual save (format = true)
10331    let save_task = editor.update_in(cx, |editor, window, cx| {
10332        editor.save(
10333            SaveOptions {
10334                format: true,
10335                autosave: false,
10336            },
10337            project.clone(),
10338            window,
10339            cx,
10340        )
10341    });
10342    save_task.await.unwrap();
10343
10344    // During manual save, clean buffers don't get written to disk
10345    // They just get did_save called for language server notifications
10346    assert_eq!(
10347        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
10348        1,
10349        "Buffer 1 should only have been written once total (during autosave, not manual save)"
10350    );
10351    assert_eq!(
10352        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
10353        0,
10354        "Buffer 2 should not have been written at all"
10355    );
10356    assert_eq!(
10357        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
10358        0,
10359        "Buffer 3 should not have been written at all"
10360    );
10361}
10362
10363async fn setup_range_format_test(
10364    cx: &mut TestAppContext,
10365) -> (
10366    Entity<Project>,
10367    Entity<Editor>,
10368    &mut gpui::VisualTestContext,
10369    lsp::FakeLanguageServer,
10370) {
10371    init_test(cx, |_| {});
10372
10373    let fs = FakeFs::new(cx.executor());
10374    fs.insert_file(path!("/file.rs"), Default::default()).await;
10375
10376    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10377
10378    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10379    language_registry.add(rust_lang());
10380    let mut fake_servers = language_registry.register_fake_lsp(
10381        "Rust",
10382        FakeLspAdapter {
10383            capabilities: lsp::ServerCapabilities {
10384                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
10385                ..lsp::ServerCapabilities::default()
10386            },
10387            ..FakeLspAdapter::default()
10388        },
10389    );
10390
10391    let buffer = project
10392        .update(cx, |project, cx| {
10393            project.open_local_buffer(path!("/file.rs"), cx)
10394        })
10395        .await
10396        .unwrap();
10397
10398    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10399    let (editor, cx) = cx.add_window_view(|window, cx| {
10400        build_editor_with_project(project.clone(), buffer, window, cx)
10401    });
10402
10403    cx.executor().start_waiting();
10404    let fake_server = fake_servers.next().await.unwrap();
10405
10406    (project, editor, cx, fake_server)
10407}
10408
10409#[gpui::test]
10410async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
10411    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
10412
10413    editor.update_in(cx, |editor, window, cx| {
10414        editor.set_text("one\ntwo\nthree\n", window, cx)
10415    });
10416    assert!(cx.read(|cx| editor.is_dirty(cx)));
10417
10418    let save = editor
10419        .update_in(cx, |editor, window, cx| {
10420            editor.save(
10421                SaveOptions {
10422                    format: true,
10423                    autosave: false,
10424                },
10425                project.clone(),
10426                window,
10427                cx,
10428            )
10429        })
10430        .unwrap();
10431    fake_server
10432        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
10433            assert_eq!(
10434                params.text_document.uri,
10435                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10436            );
10437            assert_eq!(params.options.tab_size, 4);
10438            Ok(Some(vec![lsp::TextEdit::new(
10439                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10440                ", ".to_string(),
10441            )]))
10442        })
10443        .next()
10444        .await;
10445    cx.executor().start_waiting();
10446    save.await;
10447    assert_eq!(
10448        editor.update(cx, |editor, cx| editor.text(cx)),
10449        "one, two\nthree\n"
10450    );
10451    assert!(!cx.read(|cx| editor.is_dirty(cx)));
10452}
10453
10454#[gpui::test]
10455async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
10456    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
10457
10458    editor.update_in(cx, |editor, window, cx| {
10459        editor.set_text("one\ntwo\nthree\n", window, cx)
10460    });
10461    assert!(cx.read(|cx| editor.is_dirty(cx)));
10462
10463    // Test that save still works when formatting hangs
10464    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
10465        move |params, _| async move {
10466            assert_eq!(
10467                params.text_document.uri,
10468                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10469            );
10470            futures::future::pending::<()>().await;
10471            unreachable!()
10472        },
10473    );
10474    let save = editor
10475        .update_in(cx, |editor, window, cx| {
10476            editor.save(
10477                SaveOptions {
10478                    format: true,
10479                    autosave: false,
10480                },
10481                project.clone(),
10482                window,
10483                cx,
10484            )
10485        })
10486        .unwrap();
10487    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10488    cx.executor().start_waiting();
10489    save.await;
10490    assert_eq!(
10491        editor.update(cx, |editor, cx| editor.text(cx)),
10492        "one\ntwo\nthree\n"
10493    );
10494    assert!(!cx.read(|cx| editor.is_dirty(cx)));
10495}
10496
10497#[gpui::test]
10498async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
10499    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
10500
10501    // Buffer starts clean, no formatting should be requested
10502    let save = editor
10503        .update_in(cx, |editor, window, cx| {
10504            editor.save(
10505                SaveOptions {
10506                    format: false,
10507                    autosave: false,
10508                },
10509                project.clone(),
10510                window,
10511                cx,
10512            )
10513        })
10514        .unwrap();
10515    let _pending_format_request = fake_server
10516        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
10517            panic!("Should not be invoked");
10518        })
10519        .next();
10520    cx.executor().start_waiting();
10521    save.await;
10522    cx.run_until_parked();
10523}
10524
10525#[gpui::test]
10526async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
10527    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
10528
10529    // Set Rust language override and assert overridden tabsize is sent to language server
10530    update_test_language_settings(cx, |settings| {
10531        settings.languages.0.insert(
10532            "Rust".into(),
10533            LanguageSettingsContent {
10534                tab_size: NonZeroU32::new(8),
10535                ..Default::default()
10536            },
10537        );
10538    });
10539
10540    editor.update_in(cx, |editor, window, cx| {
10541        editor.set_text("something_new\n", window, cx)
10542    });
10543    assert!(cx.read(|cx| editor.is_dirty(cx)));
10544    let save = editor
10545        .update_in(cx, |editor, window, cx| {
10546            editor.save(
10547                SaveOptions {
10548                    format: true,
10549                    autosave: false,
10550                },
10551                project.clone(),
10552                window,
10553                cx,
10554            )
10555        })
10556        .unwrap();
10557    fake_server
10558        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
10559            assert_eq!(
10560                params.text_document.uri,
10561                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10562            );
10563            assert_eq!(params.options.tab_size, 8);
10564            Ok(Some(Vec::new()))
10565        })
10566        .next()
10567        .await;
10568    save.await;
10569}
10570
10571#[gpui::test]
10572async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
10573    init_test(cx, |settings| {
10574        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
10575            Formatter::LanguageServer { name: None },
10576        )))
10577    });
10578
10579    let fs = FakeFs::new(cx.executor());
10580    fs.insert_file(path!("/file.rs"), Default::default()).await;
10581
10582    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10583
10584    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10585    language_registry.add(Arc::new(Language::new(
10586        LanguageConfig {
10587            name: "Rust".into(),
10588            matcher: LanguageMatcher {
10589                path_suffixes: vec!["rs".to_string()],
10590                ..Default::default()
10591            },
10592            ..LanguageConfig::default()
10593        },
10594        Some(tree_sitter_rust::LANGUAGE.into()),
10595    )));
10596    update_test_language_settings(cx, |settings| {
10597        // Enable Prettier formatting for the same buffer, and ensure
10598        // LSP is called instead of Prettier.
10599        settings.defaults.prettier = Some(PrettierSettings {
10600            allowed: true,
10601            ..PrettierSettings::default()
10602        });
10603    });
10604    let mut fake_servers = language_registry.register_fake_lsp(
10605        "Rust",
10606        FakeLspAdapter {
10607            capabilities: lsp::ServerCapabilities {
10608                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10609                ..Default::default()
10610            },
10611            ..Default::default()
10612        },
10613    );
10614
10615    let buffer = project
10616        .update(cx, |project, cx| {
10617            project.open_local_buffer(path!("/file.rs"), cx)
10618        })
10619        .await
10620        .unwrap();
10621
10622    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10623    let (editor, cx) = cx.add_window_view(|window, cx| {
10624        build_editor_with_project(project.clone(), buffer, window, cx)
10625    });
10626    editor.update_in(cx, |editor, window, cx| {
10627        editor.set_text("one\ntwo\nthree\n", window, cx)
10628    });
10629
10630    cx.executor().start_waiting();
10631    let fake_server = fake_servers.next().await.unwrap();
10632
10633    let format = editor
10634        .update_in(cx, |editor, window, cx| {
10635            editor.perform_format(
10636                project.clone(),
10637                FormatTrigger::Manual,
10638                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10639                window,
10640                cx,
10641            )
10642        })
10643        .unwrap();
10644    fake_server
10645        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
10646            assert_eq!(
10647                params.text_document.uri,
10648                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10649            );
10650            assert_eq!(params.options.tab_size, 4);
10651            Ok(Some(vec![lsp::TextEdit::new(
10652                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10653                ", ".to_string(),
10654            )]))
10655        })
10656        .next()
10657        .await;
10658    cx.executor().start_waiting();
10659    format.await;
10660    assert_eq!(
10661        editor.update(cx, |editor, cx| editor.text(cx)),
10662        "one, two\nthree\n"
10663    );
10664
10665    editor.update_in(cx, |editor, window, cx| {
10666        editor.set_text("one\ntwo\nthree\n", window, cx)
10667    });
10668    // Ensure we don't lock if formatting hangs.
10669    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10670        move |params, _| async move {
10671            assert_eq!(
10672                params.text_document.uri,
10673                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10674            );
10675            futures::future::pending::<()>().await;
10676            unreachable!()
10677        },
10678    );
10679    let format = editor
10680        .update_in(cx, |editor, window, cx| {
10681            editor.perform_format(
10682                project,
10683                FormatTrigger::Manual,
10684                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10685                window,
10686                cx,
10687            )
10688        })
10689        .unwrap();
10690    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10691    cx.executor().start_waiting();
10692    format.await;
10693    assert_eq!(
10694        editor.update(cx, |editor, cx| editor.text(cx)),
10695        "one\ntwo\nthree\n"
10696    );
10697}
10698
10699#[gpui::test]
10700async fn test_multiple_formatters(cx: &mut TestAppContext) {
10701    init_test(cx, |settings| {
10702        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
10703        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
10704            Formatter::LanguageServer { name: None },
10705            Formatter::CodeActions(
10706                [
10707                    ("code-action-1".into(), true),
10708                    ("code-action-2".into(), true),
10709                ]
10710                .into_iter()
10711                .collect(),
10712            ),
10713        ])))
10714    });
10715
10716    let fs = FakeFs::new(cx.executor());
10717    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
10718        .await;
10719
10720    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10721    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10722    language_registry.add(rust_lang());
10723
10724    let mut fake_servers = language_registry.register_fake_lsp(
10725        "Rust",
10726        FakeLspAdapter {
10727            capabilities: lsp::ServerCapabilities {
10728                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10729                execute_command_provider: Some(lsp::ExecuteCommandOptions {
10730                    commands: vec!["the-command-for-code-action-1".into()],
10731                    ..Default::default()
10732                }),
10733                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10734                ..Default::default()
10735            },
10736            ..Default::default()
10737        },
10738    );
10739
10740    let buffer = project
10741        .update(cx, |project, cx| {
10742            project.open_local_buffer(path!("/file.rs"), cx)
10743        })
10744        .await
10745        .unwrap();
10746
10747    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10748    let (editor, cx) = cx.add_window_view(|window, cx| {
10749        build_editor_with_project(project.clone(), buffer, window, cx)
10750    });
10751
10752    cx.executor().start_waiting();
10753
10754    let fake_server = fake_servers.next().await.unwrap();
10755    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10756        move |_params, _| async move {
10757            Ok(Some(vec![lsp::TextEdit::new(
10758                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10759                "applied-formatting\n".to_string(),
10760            )]))
10761        },
10762    );
10763    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10764        move |params, _| async move {
10765            assert_eq!(
10766                params.context.only,
10767                Some(vec!["code-action-1".into(), "code-action-2".into()])
10768            );
10769            let uri = lsp::Url::from_file_path(path!("/file.rs")).unwrap();
10770            Ok(Some(vec![
10771                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10772                    kind: Some("code-action-1".into()),
10773                    edit: Some(lsp::WorkspaceEdit::new(
10774                        [(
10775                            uri.clone(),
10776                            vec![lsp::TextEdit::new(
10777                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10778                                "applied-code-action-1-edit\n".to_string(),
10779                            )],
10780                        )]
10781                        .into_iter()
10782                        .collect(),
10783                    )),
10784                    command: Some(lsp::Command {
10785                        command: "the-command-for-code-action-1".into(),
10786                        ..Default::default()
10787                    }),
10788                    ..Default::default()
10789                }),
10790                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10791                    kind: Some("code-action-2".into()),
10792                    edit: Some(lsp::WorkspaceEdit::new(
10793                        [(
10794                            uri.clone(),
10795                            vec![lsp::TextEdit::new(
10796                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10797                                "applied-code-action-2-edit\n".to_string(),
10798                            )],
10799                        )]
10800                        .into_iter()
10801                        .collect(),
10802                    )),
10803                    ..Default::default()
10804                }),
10805            ]))
10806        },
10807    );
10808
10809    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
10810        move |params, _| async move { Ok(params) }
10811    });
10812
10813    let command_lock = Arc::new(futures::lock::Mutex::new(()));
10814    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
10815        let fake = fake_server.clone();
10816        let lock = command_lock.clone();
10817        move |params, _| {
10818            assert_eq!(params.command, "the-command-for-code-action-1");
10819            let fake = fake.clone();
10820            let lock = lock.clone();
10821            async move {
10822                lock.lock().await;
10823                fake.server
10824                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
10825                        label: None,
10826                        edit: lsp::WorkspaceEdit {
10827                            changes: Some(
10828                                [(
10829                                    lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
10830                                    vec![lsp::TextEdit {
10831                                        range: lsp::Range::new(
10832                                            lsp::Position::new(0, 0),
10833                                            lsp::Position::new(0, 0),
10834                                        ),
10835                                        new_text: "applied-code-action-1-command\n".into(),
10836                                    }],
10837                                )]
10838                                .into_iter()
10839                                .collect(),
10840                            ),
10841                            ..Default::default()
10842                        },
10843                    })
10844                    .await
10845                    .into_response()
10846                    .unwrap();
10847                Ok(Some(json!(null)))
10848            }
10849        }
10850    });
10851
10852    cx.executor().start_waiting();
10853    editor
10854        .update_in(cx, |editor, window, cx| {
10855            editor.perform_format(
10856                project.clone(),
10857                FormatTrigger::Manual,
10858                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10859                window,
10860                cx,
10861            )
10862        })
10863        .unwrap()
10864        .await;
10865    editor.update(cx, |editor, cx| {
10866        assert_eq!(
10867            editor.text(cx),
10868            r#"
10869                applied-code-action-2-edit
10870                applied-code-action-1-command
10871                applied-code-action-1-edit
10872                applied-formatting
10873                one
10874                two
10875                three
10876            "#
10877            .unindent()
10878        );
10879    });
10880
10881    editor.update_in(cx, |editor, window, cx| {
10882        editor.undo(&Default::default(), window, cx);
10883        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10884    });
10885
10886    // Perform a manual edit while waiting for an LSP command
10887    // that's being run as part of a formatting code action.
10888    let lock_guard = command_lock.lock().await;
10889    let format = editor
10890        .update_in(cx, |editor, window, cx| {
10891            editor.perform_format(
10892                project.clone(),
10893                FormatTrigger::Manual,
10894                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10895                window,
10896                cx,
10897            )
10898        })
10899        .unwrap();
10900    cx.run_until_parked();
10901    editor.update(cx, |editor, cx| {
10902        assert_eq!(
10903            editor.text(cx),
10904            r#"
10905                applied-code-action-1-edit
10906                applied-formatting
10907                one
10908                two
10909                three
10910            "#
10911            .unindent()
10912        );
10913
10914        editor.buffer.update(cx, |buffer, cx| {
10915            let ix = buffer.len(cx);
10916            buffer.edit([(ix..ix, "edited\n")], None, cx);
10917        });
10918    });
10919
10920    // Allow the LSP command to proceed. Because the buffer was edited,
10921    // the second code action will not be run.
10922    drop(lock_guard);
10923    format.await;
10924    editor.update_in(cx, |editor, window, cx| {
10925        assert_eq!(
10926            editor.text(cx),
10927            r#"
10928                applied-code-action-1-command
10929                applied-code-action-1-edit
10930                applied-formatting
10931                one
10932                two
10933                three
10934                edited
10935            "#
10936            .unindent()
10937        );
10938
10939        // The manual edit is undone first, because it is the last thing the user did
10940        // (even though the command completed afterwards).
10941        editor.undo(&Default::default(), window, cx);
10942        assert_eq!(
10943            editor.text(cx),
10944            r#"
10945                applied-code-action-1-command
10946                applied-code-action-1-edit
10947                applied-formatting
10948                one
10949                two
10950                three
10951            "#
10952            .unindent()
10953        );
10954
10955        // All the formatting (including the command, which completed after the manual edit)
10956        // is undone together.
10957        editor.undo(&Default::default(), window, cx);
10958        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10959    });
10960}
10961
10962#[gpui::test]
10963async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
10964    init_test(cx, |settings| {
10965        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
10966            Formatter::LanguageServer { name: None },
10967        ])))
10968    });
10969
10970    let fs = FakeFs::new(cx.executor());
10971    fs.insert_file(path!("/file.ts"), Default::default()).await;
10972
10973    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10974
10975    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10976    language_registry.add(Arc::new(Language::new(
10977        LanguageConfig {
10978            name: "TypeScript".into(),
10979            matcher: LanguageMatcher {
10980                path_suffixes: vec!["ts".to_string()],
10981                ..Default::default()
10982            },
10983            ..LanguageConfig::default()
10984        },
10985        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
10986    )));
10987    update_test_language_settings(cx, |settings| {
10988        settings.defaults.prettier = Some(PrettierSettings {
10989            allowed: true,
10990            ..PrettierSettings::default()
10991        });
10992    });
10993    let mut fake_servers = language_registry.register_fake_lsp(
10994        "TypeScript",
10995        FakeLspAdapter {
10996            capabilities: lsp::ServerCapabilities {
10997                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10998                ..Default::default()
10999            },
11000            ..Default::default()
11001        },
11002    );
11003
11004    let buffer = project
11005        .update(cx, |project, cx| {
11006            project.open_local_buffer(path!("/file.ts"), cx)
11007        })
11008        .await
11009        .unwrap();
11010
11011    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11012    let (editor, cx) = cx.add_window_view(|window, cx| {
11013        build_editor_with_project(project.clone(), buffer, window, cx)
11014    });
11015    editor.update_in(cx, |editor, window, cx| {
11016        editor.set_text(
11017            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
11018            window,
11019            cx,
11020        )
11021    });
11022
11023    cx.executor().start_waiting();
11024    let fake_server = fake_servers.next().await.unwrap();
11025
11026    let format = editor
11027        .update_in(cx, |editor, window, cx| {
11028            editor.perform_code_action_kind(
11029                project.clone(),
11030                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
11031                window,
11032                cx,
11033            )
11034        })
11035        .unwrap();
11036    fake_server
11037        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
11038            assert_eq!(
11039                params.text_document.uri,
11040                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
11041            );
11042            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
11043                lsp::CodeAction {
11044                    title: "Organize Imports".to_string(),
11045                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
11046                    edit: Some(lsp::WorkspaceEdit {
11047                        changes: Some(
11048                            [(
11049                                params.text_document.uri.clone(),
11050                                vec![lsp::TextEdit::new(
11051                                    lsp::Range::new(
11052                                        lsp::Position::new(1, 0),
11053                                        lsp::Position::new(2, 0),
11054                                    ),
11055                                    "".to_string(),
11056                                )],
11057                            )]
11058                            .into_iter()
11059                            .collect(),
11060                        ),
11061                        ..Default::default()
11062                    }),
11063                    ..Default::default()
11064                },
11065            )]))
11066        })
11067        .next()
11068        .await;
11069    cx.executor().start_waiting();
11070    format.await;
11071    assert_eq!(
11072        editor.update(cx, |editor, cx| editor.text(cx)),
11073        "import { a } from 'module';\n\nconst x = a;\n"
11074    );
11075
11076    editor.update_in(cx, |editor, window, cx| {
11077        editor.set_text(
11078            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
11079            window,
11080            cx,
11081        )
11082    });
11083    // Ensure we don't lock if code action hangs.
11084    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
11085        move |params, _| async move {
11086            assert_eq!(
11087                params.text_document.uri,
11088                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
11089            );
11090            futures::future::pending::<()>().await;
11091            unreachable!()
11092        },
11093    );
11094    let format = editor
11095        .update_in(cx, |editor, window, cx| {
11096            editor.perform_code_action_kind(
11097                project,
11098                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
11099                window,
11100                cx,
11101            )
11102        })
11103        .unwrap();
11104    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
11105    cx.executor().start_waiting();
11106    format.await;
11107    assert_eq!(
11108        editor.update(cx, |editor, cx| editor.text(cx)),
11109        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
11110    );
11111}
11112
11113#[gpui::test]
11114async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
11115    init_test(cx, |_| {});
11116
11117    let mut cx = EditorLspTestContext::new_rust(
11118        lsp::ServerCapabilities {
11119            document_formatting_provider: Some(lsp::OneOf::Left(true)),
11120            ..Default::default()
11121        },
11122        cx,
11123    )
11124    .await;
11125
11126    cx.set_state(indoc! {"
11127        one.twoˇ
11128    "});
11129
11130    // The format request takes a long time. When it completes, it inserts
11131    // a newline and an indent before the `.`
11132    cx.lsp
11133        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
11134            let executor = cx.background_executor().clone();
11135            async move {
11136                executor.timer(Duration::from_millis(100)).await;
11137                Ok(Some(vec![lsp::TextEdit {
11138                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
11139                    new_text: "\n    ".into(),
11140                }]))
11141            }
11142        });
11143
11144    // Submit a format request.
11145    let format_1 = cx
11146        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
11147        .unwrap();
11148    cx.executor().run_until_parked();
11149
11150    // Submit a second format request.
11151    let format_2 = cx
11152        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
11153        .unwrap();
11154    cx.executor().run_until_parked();
11155
11156    // Wait for both format requests to complete
11157    cx.executor().advance_clock(Duration::from_millis(200));
11158    cx.executor().start_waiting();
11159    format_1.await.unwrap();
11160    cx.executor().start_waiting();
11161    format_2.await.unwrap();
11162
11163    // The formatting edits only happens once.
11164    cx.assert_editor_state(indoc! {"
11165        one
11166            .twoˇ
11167    "});
11168}
11169
11170#[gpui::test]
11171async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
11172    init_test(cx, |settings| {
11173        settings.defaults.formatter = Some(SelectedFormatter::Auto)
11174    });
11175
11176    let mut cx = EditorLspTestContext::new_rust(
11177        lsp::ServerCapabilities {
11178            document_formatting_provider: Some(lsp::OneOf::Left(true)),
11179            ..Default::default()
11180        },
11181        cx,
11182    )
11183    .await;
11184
11185    // Set up a buffer white some trailing whitespace and no trailing newline.
11186    cx.set_state(
11187        &[
11188            "one ",   //
11189            "twoˇ",   //
11190            "three ", //
11191            "four",   //
11192        ]
11193        .join("\n"),
11194    );
11195
11196    // Submit a format request.
11197    let format = cx
11198        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
11199        .unwrap();
11200
11201    // Record which buffer changes have been sent to the language server
11202    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
11203    cx.lsp
11204        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
11205            let buffer_changes = buffer_changes.clone();
11206            move |params, _| {
11207                buffer_changes.lock().extend(
11208                    params
11209                        .content_changes
11210                        .into_iter()
11211                        .map(|e| (e.range.unwrap(), e.text)),
11212                );
11213            }
11214        });
11215
11216    // Handle formatting requests to the language server.
11217    cx.lsp
11218        .set_request_handler::<lsp::request::Formatting, _, _>({
11219            let buffer_changes = buffer_changes.clone();
11220            move |_, _| {
11221                // When formatting is requested, trailing whitespace has already been stripped,
11222                // and the trailing newline has already been added.
11223                assert_eq!(
11224                    &buffer_changes.lock()[1..],
11225                    &[
11226                        (
11227                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
11228                            "".into()
11229                        ),
11230                        (
11231                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
11232                            "".into()
11233                        ),
11234                        (
11235                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
11236                            "\n".into()
11237                        ),
11238                    ]
11239                );
11240
11241                // Insert blank lines between each line of the buffer.
11242                async move {
11243                    Ok(Some(vec![
11244                        lsp::TextEdit {
11245                            range: lsp::Range::new(
11246                                lsp::Position::new(1, 0),
11247                                lsp::Position::new(1, 0),
11248                            ),
11249                            new_text: "\n".into(),
11250                        },
11251                        lsp::TextEdit {
11252                            range: lsp::Range::new(
11253                                lsp::Position::new(2, 0),
11254                                lsp::Position::new(2, 0),
11255                            ),
11256                            new_text: "\n".into(),
11257                        },
11258                    ]))
11259                }
11260            }
11261        });
11262
11263    // After formatting the buffer, the trailing whitespace is stripped,
11264    // a newline is appended, and the edits provided by the language server
11265    // have been applied.
11266    format.await.unwrap();
11267    cx.assert_editor_state(
11268        &[
11269            "one",   //
11270            "",      //
11271            "twoˇ",  //
11272            "",      //
11273            "three", //
11274            "four",  //
11275            "",      //
11276        ]
11277        .join("\n"),
11278    );
11279
11280    // Undoing the formatting undoes the trailing whitespace removal, the
11281    // trailing newline, and the LSP edits.
11282    cx.update_buffer(|buffer, cx| buffer.undo(cx));
11283    cx.assert_editor_state(
11284        &[
11285            "one ",   //
11286            "twoˇ",   //
11287            "three ", //
11288            "four",   //
11289        ]
11290        .join("\n"),
11291    );
11292}
11293
11294#[gpui::test]
11295async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
11296    cx: &mut TestAppContext,
11297) {
11298    init_test(cx, |_| {});
11299
11300    cx.update(|cx| {
11301        cx.update_global::<SettingsStore, _>(|settings, cx| {
11302            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11303                settings.auto_signature_help = Some(true);
11304            });
11305        });
11306    });
11307
11308    let mut cx = EditorLspTestContext::new_rust(
11309        lsp::ServerCapabilities {
11310            signature_help_provider: Some(lsp::SignatureHelpOptions {
11311                ..Default::default()
11312            }),
11313            ..Default::default()
11314        },
11315        cx,
11316    )
11317    .await;
11318
11319    let language = Language::new(
11320        LanguageConfig {
11321            name: "Rust".into(),
11322            brackets: BracketPairConfig {
11323                pairs: vec![
11324                    BracketPair {
11325                        start: "{".to_string(),
11326                        end: "}".to_string(),
11327                        close: true,
11328                        surround: true,
11329                        newline: true,
11330                    },
11331                    BracketPair {
11332                        start: "(".to_string(),
11333                        end: ")".to_string(),
11334                        close: true,
11335                        surround: true,
11336                        newline: true,
11337                    },
11338                    BracketPair {
11339                        start: "/*".to_string(),
11340                        end: " */".to_string(),
11341                        close: true,
11342                        surround: true,
11343                        newline: true,
11344                    },
11345                    BracketPair {
11346                        start: "[".to_string(),
11347                        end: "]".to_string(),
11348                        close: false,
11349                        surround: false,
11350                        newline: true,
11351                    },
11352                    BracketPair {
11353                        start: "\"".to_string(),
11354                        end: "\"".to_string(),
11355                        close: true,
11356                        surround: true,
11357                        newline: false,
11358                    },
11359                    BracketPair {
11360                        start: "<".to_string(),
11361                        end: ">".to_string(),
11362                        close: false,
11363                        surround: true,
11364                        newline: true,
11365                    },
11366                ],
11367                ..Default::default()
11368            },
11369            autoclose_before: "})]".to_string(),
11370            ..Default::default()
11371        },
11372        Some(tree_sitter_rust::LANGUAGE.into()),
11373    );
11374    let language = Arc::new(language);
11375
11376    cx.language_registry().add(language.clone());
11377    cx.update_buffer(|buffer, cx| {
11378        buffer.set_language(Some(language), cx);
11379    });
11380
11381    cx.set_state(
11382        &r#"
11383            fn main() {
11384                sampleˇ
11385            }
11386        "#
11387        .unindent(),
11388    );
11389
11390    cx.update_editor(|editor, window, cx| {
11391        editor.handle_input("(", window, cx);
11392    });
11393    cx.assert_editor_state(
11394        &"
11395            fn main() {
11396                sample(ˇ)
11397            }
11398        "
11399        .unindent(),
11400    );
11401
11402    let mocked_response = lsp::SignatureHelp {
11403        signatures: vec![lsp::SignatureInformation {
11404            label: "fn sample(param1: u8, param2: u8)".to_string(),
11405            documentation: None,
11406            parameters: Some(vec![
11407                lsp::ParameterInformation {
11408                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11409                    documentation: None,
11410                },
11411                lsp::ParameterInformation {
11412                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11413                    documentation: None,
11414                },
11415            ]),
11416            active_parameter: None,
11417        }],
11418        active_signature: Some(0),
11419        active_parameter: Some(0),
11420    };
11421    handle_signature_help_request(&mut cx, mocked_response).await;
11422
11423    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11424        .await;
11425
11426    cx.editor(|editor, _, _| {
11427        let signature_help_state = editor.signature_help_state.popover().cloned();
11428        let signature = signature_help_state.unwrap();
11429        assert_eq!(
11430            signature.signatures[signature.current_signature].label,
11431            "fn sample(param1: u8, param2: u8)"
11432        );
11433    });
11434}
11435
11436#[gpui::test]
11437async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
11438    init_test(cx, |_| {});
11439
11440    cx.update(|cx| {
11441        cx.update_global::<SettingsStore, _>(|settings, cx| {
11442            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11443                settings.auto_signature_help = Some(false);
11444                settings.show_signature_help_after_edits = Some(false);
11445            });
11446        });
11447    });
11448
11449    let mut cx = EditorLspTestContext::new_rust(
11450        lsp::ServerCapabilities {
11451            signature_help_provider: Some(lsp::SignatureHelpOptions {
11452                ..Default::default()
11453            }),
11454            ..Default::default()
11455        },
11456        cx,
11457    )
11458    .await;
11459
11460    let language = Language::new(
11461        LanguageConfig {
11462            name: "Rust".into(),
11463            brackets: BracketPairConfig {
11464                pairs: vec![
11465                    BracketPair {
11466                        start: "{".to_string(),
11467                        end: "}".to_string(),
11468                        close: true,
11469                        surround: true,
11470                        newline: true,
11471                    },
11472                    BracketPair {
11473                        start: "(".to_string(),
11474                        end: ")".to_string(),
11475                        close: true,
11476                        surround: true,
11477                        newline: true,
11478                    },
11479                    BracketPair {
11480                        start: "/*".to_string(),
11481                        end: " */".to_string(),
11482                        close: true,
11483                        surround: true,
11484                        newline: true,
11485                    },
11486                    BracketPair {
11487                        start: "[".to_string(),
11488                        end: "]".to_string(),
11489                        close: false,
11490                        surround: false,
11491                        newline: true,
11492                    },
11493                    BracketPair {
11494                        start: "\"".to_string(),
11495                        end: "\"".to_string(),
11496                        close: true,
11497                        surround: true,
11498                        newline: false,
11499                    },
11500                    BracketPair {
11501                        start: "<".to_string(),
11502                        end: ">".to_string(),
11503                        close: false,
11504                        surround: true,
11505                        newline: true,
11506                    },
11507                ],
11508                ..Default::default()
11509            },
11510            autoclose_before: "})]".to_string(),
11511            ..Default::default()
11512        },
11513        Some(tree_sitter_rust::LANGUAGE.into()),
11514    );
11515    let language = Arc::new(language);
11516
11517    cx.language_registry().add(language.clone());
11518    cx.update_buffer(|buffer, cx| {
11519        buffer.set_language(Some(language), cx);
11520    });
11521
11522    // Ensure that signature_help is not called when no signature help is enabled.
11523    cx.set_state(
11524        &r#"
11525            fn main() {
11526                sampleˇ
11527            }
11528        "#
11529        .unindent(),
11530    );
11531    cx.update_editor(|editor, window, cx| {
11532        editor.handle_input("(", window, cx);
11533    });
11534    cx.assert_editor_state(
11535        &"
11536            fn main() {
11537                sample(ˇ)
11538            }
11539        "
11540        .unindent(),
11541    );
11542    cx.editor(|editor, _, _| {
11543        assert!(editor.signature_help_state.task().is_none());
11544    });
11545
11546    let mocked_response = lsp::SignatureHelp {
11547        signatures: vec![lsp::SignatureInformation {
11548            label: "fn sample(param1: u8, param2: u8)".to_string(),
11549            documentation: None,
11550            parameters: Some(vec![
11551                lsp::ParameterInformation {
11552                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11553                    documentation: None,
11554                },
11555                lsp::ParameterInformation {
11556                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11557                    documentation: None,
11558                },
11559            ]),
11560            active_parameter: None,
11561        }],
11562        active_signature: Some(0),
11563        active_parameter: Some(0),
11564    };
11565
11566    // Ensure that signature_help is called when enabled afte edits
11567    cx.update(|_, cx| {
11568        cx.update_global::<SettingsStore, _>(|settings, cx| {
11569            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11570                settings.auto_signature_help = Some(false);
11571                settings.show_signature_help_after_edits = Some(true);
11572            });
11573        });
11574    });
11575    cx.set_state(
11576        &r#"
11577            fn main() {
11578                sampleˇ
11579            }
11580        "#
11581        .unindent(),
11582    );
11583    cx.update_editor(|editor, window, cx| {
11584        editor.handle_input("(", window, cx);
11585    });
11586    cx.assert_editor_state(
11587        &"
11588            fn main() {
11589                sample(ˇ)
11590            }
11591        "
11592        .unindent(),
11593    );
11594    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11595    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11596        .await;
11597    cx.update_editor(|editor, _, _| {
11598        let signature_help_state = editor.signature_help_state.popover().cloned();
11599        assert!(signature_help_state.is_some());
11600        let signature = signature_help_state.unwrap();
11601        assert_eq!(
11602            signature.signatures[signature.current_signature].label,
11603            "fn sample(param1: u8, param2: u8)"
11604        );
11605        editor.signature_help_state = SignatureHelpState::default();
11606    });
11607
11608    // Ensure that signature_help is called when auto signature help override is enabled
11609    cx.update(|_, cx| {
11610        cx.update_global::<SettingsStore, _>(|settings, cx| {
11611            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11612                settings.auto_signature_help = Some(true);
11613                settings.show_signature_help_after_edits = Some(false);
11614            });
11615        });
11616    });
11617    cx.set_state(
11618        &r#"
11619            fn main() {
11620                sampleˇ
11621            }
11622        "#
11623        .unindent(),
11624    );
11625    cx.update_editor(|editor, window, cx| {
11626        editor.handle_input("(", window, cx);
11627    });
11628    cx.assert_editor_state(
11629        &"
11630            fn main() {
11631                sample(ˇ)
11632            }
11633        "
11634        .unindent(),
11635    );
11636    handle_signature_help_request(&mut cx, mocked_response).await;
11637    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11638        .await;
11639    cx.editor(|editor, _, _| {
11640        let signature_help_state = editor.signature_help_state.popover().cloned();
11641        assert!(signature_help_state.is_some());
11642        let signature = signature_help_state.unwrap();
11643        assert_eq!(
11644            signature.signatures[signature.current_signature].label,
11645            "fn sample(param1: u8, param2: u8)"
11646        );
11647    });
11648}
11649
11650#[gpui::test]
11651async fn test_signature_help(cx: &mut TestAppContext) {
11652    init_test(cx, |_| {});
11653    cx.update(|cx| {
11654        cx.update_global::<SettingsStore, _>(|settings, cx| {
11655            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11656                settings.auto_signature_help = Some(true);
11657            });
11658        });
11659    });
11660
11661    let mut cx = EditorLspTestContext::new_rust(
11662        lsp::ServerCapabilities {
11663            signature_help_provider: Some(lsp::SignatureHelpOptions {
11664                ..Default::default()
11665            }),
11666            ..Default::default()
11667        },
11668        cx,
11669    )
11670    .await;
11671
11672    // A test that directly calls `show_signature_help`
11673    cx.update_editor(|editor, window, cx| {
11674        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11675    });
11676
11677    let mocked_response = lsp::SignatureHelp {
11678        signatures: vec![lsp::SignatureInformation {
11679            label: "fn sample(param1: u8, param2: u8)".to_string(),
11680            documentation: None,
11681            parameters: Some(vec![
11682                lsp::ParameterInformation {
11683                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11684                    documentation: None,
11685                },
11686                lsp::ParameterInformation {
11687                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11688                    documentation: None,
11689                },
11690            ]),
11691            active_parameter: None,
11692        }],
11693        active_signature: Some(0),
11694        active_parameter: Some(0),
11695    };
11696    handle_signature_help_request(&mut cx, mocked_response).await;
11697
11698    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11699        .await;
11700
11701    cx.editor(|editor, _, _| {
11702        let signature_help_state = editor.signature_help_state.popover().cloned();
11703        assert!(signature_help_state.is_some());
11704        let signature = signature_help_state.unwrap();
11705        assert_eq!(
11706            signature.signatures[signature.current_signature].label,
11707            "fn sample(param1: u8, param2: u8)"
11708        );
11709    });
11710
11711    // When exiting outside from inside the brackets, `signature_help` is closed.
11712    cx.set_state(indoc! {"
11713        fn main() {
11714            sample(ˇ);
11715        }
11716
11717        fn sample(param1: u8, param2: u8) {}
11718    "});
11719
11720    cx.update_editor(|editor, window, cx| {
11721        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11722            s.select_ranges([0..0])
11723        });
11724    });
11725
11726    let mocked_response = lsp::SignatureHelp {
11727        signatures: Vec::new(),
11728        active_signature: None,
11729        active_parameter: None,
11730    };
11731    handle_signature_help_request(&mut cx, mocked_response).await;
11732
11733    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11734        .await;
11735
11736    cx.editor(|editor, _, _| {
11737        assert!(!editor.signature_help_state.is_shown());
11738    });
11739
11740    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
11741    cx.set_state(indoc! {"
11742        fn main() {
11743            sample(ˇ);
11744        }
11745
11746        fn sample(param1: u8, param2: u8) {}
11747    "});
11748
11749    let mocked_response = lsp::SignatureHelp {
11750        signatures: vec![lsp::SignatureInformation {
11751            label: "fn sample(param1: u8, param2: u8)".to_string(),
11752            documentation: None,
11753            parameters: Some(vec![
11754                lsp::ParameterInformation {
11755                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11756                    documentation: None,
11757                },
11758                lsp::ParameterInformation {
11759                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11760                    documentation: None,
11761                },
11762            ]),
11763            active_parameter: None,
11764        }],
11765        active_signature: Some(0),
11766        active_parameter: Some(0),
11767    };
11768    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11769    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11770        .await;
11771    cx.editor(|editor, _, _| {
11772        assert!(editor.signature_help_state.is_shown());
11773    });
11774
11775    // Restore the popover with more parameter input
11776    cx.set_state(indoc! {"
11777        fn main() {
11778            sample(param1, param2ˇ);
11779        }
11780
11781        fn sample(param1: u8, param2: u8) {}
11782    "});
11783
11784    let mocked_response = lsp::SignatureHelp {
11785        signatures: vec![lsp::SignatureInformation {
11786            label: "fn sample(param1: u8, param2: u8)".to_string(),
11787            documentation: None,
11788            parameters: Some(vec![
11789                lsp::ParameterInformation {
11790                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11791                    documentation: None,
11792                },
11793                lsp::ParameterInformation {
11794                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11795                    documentation: None,
11796                },
11797            ]),
11798            active_parameter: None,
11799        }],
11800        active_signature: Some(0),
11801        active_parameter: Some(1),
11802    };
11803    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11804    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11805        .await;
11806
11807    // When selecting a range, the popover is gone.
11808    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
11809    cx.update_editor(|editor, window, cx| {
11810        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11811            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11812        })
11813    });
11814    cx.assert_editor_state(indoc! {"
11815        fn main() {
11816            sample(param1, «ˇparam2»);
11817        }
11818
11819        fn sample(param1: u8, param2: u8) {}
11820    "});
11821    cx.editor(|editor, _, _| {
11822        assert!(!editor.signature_help_state.is_shown());
11823    });
11824
11825    // When unselecting again, the popover is back if within the brackets.
11826    cx.update_editor(|editor, window, cx| {
11827        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11828            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11829        })
11830    });
11831    cx.assert_editor_state(indoc! {"
11832        fn main() {
11833            sample(param1, ˇparam2);
11834        }
11835
11836        fn sample(param1: u8, param2: u8) {}
11837    "});
11838    handle_signature_help_request(&mut cx, mocked_response).await;
11839    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11840        .await;
11841    cx.editor(|editor, _, _| {
11842        assert!(editor.signature_help_state.is_shown());
11843    });
11844
11845    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
11846    cx.update_editor(|editor, window, cx| {
11847        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11848            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
11849            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11850        })
11851    });
11852    cx.assert_editor_state(indoc! {"
11853        fn main() {
11854            sample(param1, ˇparam2);
11855        }
11856
11857        fn sample(param1: u8, param2: u8) {}
11858    "});
11859
11860    let mocked_response = lsp::SignatureHelp {
11861        signatures: vec![lsp::SignatureInformation {
11862            label: "fn sample(param1: u8, param2: u8)".to_string(),
11863            documentation: None,
11864            parameters: Some(vec![
11865                lsp::ParameterInformation {
11866                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11867                    documentation: None,
11868                },
11869                lsp::ParameterInformation {
11870                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11871                    documentation: None,
11872                },
11873            ]),
11874            active_parameter: None,
11875        }],
11876        active_signature: Some(0),
11877        active_parameter: Some(1),
11878    };
11879    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11880    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11881        .await;
11882    cx.update_editor(|editor, _, cx| {
11883        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
11884    });
11885    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11886        .await;
11887    cx.update_editor(|editor, window, cx| {
11888        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11889            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11890        })
11891    });
11892    cx.assert_editor_state(indoc! {"
11893        fn main() {
11894            sample(param1, «ˇparam2»);
11895        }
11896
11897        fn sample(param1: u8, param2: u8) {}
11898    "});
11899    cx.update_editor(|editor, window, cx| {
11900        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11901            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11902        })
11903    });
11904    cx.assert_editor_state(indoc! {"
11905        fn main() {
11906            sample(param1, ˇparam2);
11907        }
11908
11909        fn sample(param1: u8, param2: u8) {}
11910    "});
11911    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
11912        .await;
11913}
11914
11915#[gpui::test]
11916async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
11917    init_test(cx, |_| {});
11918
11919    let mut cx = EditorLspTestContext::new_rust(
11920        lsp::ServerCapabilities {
11921            signature_help_provider: Some(lsp::SignatureHelpOptions {
11922                ..Default::default()
11923            }),
11924            ..Default::default()
11925        },
11926        cx,
11927    )
11928    .await;
11929
11930    cx.set_state(indoc! {"
11931        fn main() {
11932            overloadedˇ
11933        }
11934    "});
11935
11936    cx.update_editor(|editor, window, cx| {
11937        editor.handle_input("(", window, cx);
11938        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11939    });
11940
11941    // Mock response with 3 signatures
11942    let mocked_response = lsp::SignatureHelp {
11943        signatures: vec![
11944            lsp::SignatureInformation {
11945                label: "fn overloaded(x: i32)".to_string(),
11946                documentation: None,
11947                parameters: Some(vec![lsp::ParameterInformation {
11948                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11949                    documentation: None,
11950                }]),
11951                active_parameter: None,
11952            },
11953            lsp::SignatureInformation {
11954                label: "fn overloaded(x: i32, y: i32)".to_string(),
11955                documentation: None,
11956                parameters: Some(vec![
11957                    lsp::ParameterInformation {
11958                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11959                        documentation: None,
11960                    },
11961                    lsp::ParameterInformation {
11962                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
11963                        documentation: None,
11964                    },
11965                ]),
11966                active_parameter: None,
11967            },
11968            lsp::SignatureInformation {
11969                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
11970                documentation: None,
11971                parameters: Some(vec![
11972                    lsp::ParameterInformation {
11973                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11974                        documentation: None,
11975                    },
11976                    lsp::ParameterInformation {
11977                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
11978                        documentation: None,
11979                    },
11980                    lsp::ParameterInformation {
11981                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
11982                        documentation: None,
11983                    },
11984                ]),
11985                active_parameter: None,
11986            },
11987        ],
11988        active_signature: Some(1),
11989        active_parameter: Some(0),
11990    };
11991    handle_signature_help_request(&mut cx, mocked_response).await;
11992
11993    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11994        .await;
11995
11996    // Verify we have multiple signatures and the right one is selected
11997    cx.editor(|editor, _, _| {
11998        let popover = editor.signature_help_state.popover().cloned().unwrap();
11999        assert_eq!(popover.signatures.len(), 3);
12000        // active_signature was 1, so that should be the current
12001        assert_eq!(popover.current_signature, 1);
12002        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
12003        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
12004        assert_eq!(
12005            popover.signatures[2].label,
12006            "fn overloaded(x: i32, y: i32, z: i32)"
12007        );
12008    });
12009
12010    // Test navigation functionality
12011    cx.update_editor(|editor, window, cx| {
12012        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
12013    });
12014
12015    cx.editor(|editor, _, _| {
12016        let popover = editor.signature_help_state.popover().cloned().unwrap();
12017        assert_eq!(popover.current_signature, 2);
12018    });
12019
12020    // Test wrap around
12021    cx.update_editor(|editor, window, cx| {
12022        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
12023    });
12024
12025    cx.editor(|editor, _, _| {
12026        let popover = editor.signature_help_state.popover().cloned().unwrap();
12027        assert_eq!(popover.current_signature, 0);
12028    });
12029
12030    // Test previous navigation
12031    cx.update_editor(|editor, window, cx| {
12032        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
12033    });
12034
12035    cx.editor(|editor, _, _| {
12036        let popover = editor.signature_help_state.popover().cloned().unwrap();
12037        assert_eq!(popover.current_signature, 2);
12038    });
12039}
12040
12041#[gpui::test]
12042async fn test_completion_mode(cx: &mut TestAppContext) {
12043    init_test(cx, |_| {});
12044    let mut cx = EditorLspTestContext::new_rust(
12045        lsp::ServerCapabilities {
12046            completion_provider: Some(lsp::CompletionOptions {
12047                resolve_provider: Some(true),
12048                ..Default::default()
12049            }),
12050            ..Default::default()
12051        },
12052        cx,
12053    )
12054    .await;
12055
12056    struct Run {
12057        run_description: &'static str,
12058        initial_state: String,
12059        buffer_marked_text: String,
12060        completion_label: &'static str,
12061        completion_text: &'static str,
12062        expected_with_insert_mode: String,
12063        expected_with_replace_mode: String,
12064        expected_with_replace_subsequence_mode: String,
12065        expected_with_replace_suffix_mode: String,
12066    }
12067
12068    let runs = [
12069        Run {
12070            run_description: "Start of word matches completion text",
12071            initial_state: "before ediˇ after".into(),
12072            buffer_marked_text: "before <edi|> after".into(),
12073            completion_label: "editor",
12074            completion_text: "editor",
12075            expected_with_insert_mode: "before editorˇ after".into(),
12076            expected_with_replace_mode: "before editorˇ after".into(),
12077            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
12078            expected_with_replace_suffix_mode: "before editorˇ after".into(),
12079        },
12080        Run {
12081            run_description: "Accept same text at the middle of the word",
12082            initial_state: "before ediˇtor after".into(),
12083            buffer_marked_text: "before <edi|tor> after".into(),
12084            completion_label: "editor",
12085            completion_text: "editor",
12086            expected_with_insert_mode: "before editorˇtor after".into(),
12087            expected_with_replace_mode: "before editorˇ after".into(),
12088            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
12089            expected_with_replace_suffix_mode: "before editorˇ after".into(),
12090        },
12091        Run {
12092            run_description: "End of word matches completion text -- cursor at end",
12093            initial_state: "before torˇ after".into(),
12094            buffer_marked_text: "before <tor|> after".into(),
12095            completion_label: "editor",
12096            completion_text: "editor",
12097            expected_with_insert_mode: "before editorˇ after".into(),
12098            expected_with_replace_mode: "before editorˇ after".into(),
12099            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
12100            expected_with_replace_suffix_mode: "before editorˇ after".into(),
12101        },
12102        Run {
12103            run_description: "End of word matches completion text -- cursor at start",
12104            initial_state: "before ˇtor after".into(),
12105            buffer_marked_text: "before <|tor> after".into(),
12106            completion_label: "editor",
12107            completion_text: "editor",
12108            expected_with_insert_mode: "before editorˇtor after".into(),
12109            expected_with_replace_mode: "before editorˇ after".into(),
12110            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
12111            expected_with_replace_suffix_mode: "before editorˇ after".into(),
12112        },
12113        Run {
12114            run_description: "Prepend text containing whitespace",
12115            initial_state: "pˇfield: bool".into(),
12116            buffer_marked_text: "<p|field>: bool".into(),
12117            completion_label: "pub ",
12118            completion_text: "pub ",
12119            expected_with_insert_mode: "pub ˇfield: bool".into(),
12120            expected_with_replace_mode: "pub ˇ: bool".into(),
12121            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
12122            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
12123        },
12124        Run {
12125            run_description: "Add element to start of list",
12126            initial_state: "[element_ˇelement_2]".into(),
12127            buffer_marked_text: "[<element_|element_2>]".into(),
12128            completion_label: "element_1",
12129            completion_text: "element_1",
12130            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
12131            expected_with_replace_mode: "[element_1ˇ]".into(),
12132            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
12133            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
12134        },
12135        Run {
12136            run_description: "Add element to start of list -- first and second elements are equal",
12137            initial_state: "[elˇelement]".into(),
12138            buffer_marked_text: "[<el|element>]".into(),
12139            completion_label: "element",
12140            completion_text: "element",
12141            expected_with_insert_mode: "[elementˇelement]".into(),
12142            expected_with_replace_mode: "[elementˇ]".into(),
12143            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
12144            expected_with_replace_suffix_mode: "[elementˇ]".into(),
12145        },
12146        Run {
12147            run_description: "Ends with matching suffix",
12148            initial_state: "SubˇError".into(),
12149            buffer_marked_text: "<Sub|Error>".into(),
12150            completion_label: "SubscriptionError",
12151            completion_text: "SubscriptionError",
12152            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
12153            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
12154            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
12155            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
12156        },
12157        Run {
12158            run_description: "Suffix is a subsequence -- contiguous",
12159            initial_state: "SubˇErr".into(),
12160            buffer_marked_text: "<Sub|Err>".into(),
12161            completion_label: "SubscriptionError",
12162            completion_text: "SubscriptionError",
12163            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
12164            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
12165            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
12166            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
12167        },
12168        Run {
12169            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
12170            initial_state: "Suˇscrirr".into(),
12171            buffer_marked_text: "<Su|scrirr>".into(),
12172            completion_label: "SubscriptionError",
12173            completion_text: "SubscriptionError",
12174            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
12175            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
12176            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
12177            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
12178        },
12179        Run {
12180            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
12181            initial_state: "foo(indˇix)".into(),
12182            buffer_marked_text: "foo(<ind|ix>)".into(),
12183            completion_label: "node_index",
12184            completion_text: "node_index",
12185            expected_with_insert_mode: "foo(node_indexˇix)".into(),
12186            expected_with_replace_mode: "foo(node_indexˇ)".into(),
12187            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
12188            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
12189        },
12190        Run {
12191            run_description: "Replace range ends before cursor - should extend to cursor",
12192            initial_state: "before editˇo after".into(),
12193            buffer_marked_text: "before <{ed}>it|o after".into(),
12194            completion_label: "editor",
12195            completion_text: "editor",
12196            expected_with_insert_mode: "before editorˇo after".into(),
12197            expected_with_replace_mode: "before editorˇo after".into(),
12198            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
12199            expected_with_replace_suffix_mode: "before editorˇo after".into(),
12200        },
12201        Run {
12202            run_description: "Uses label for suffix matching",
12203            initial_state: "before ediˇtor after".into(),
12204            buffer_marked_text: "before <edi|tor> after".into(),
12205            completion_label: "editor",
12206            completion_text: "editor()",
12207            expected_with_insert_mode: "before editor()ˇtor after".into(),
12208            expected_with_replace_mode: "before editor()ˇ after".into(),
12209            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
12210            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
12211        },
12212        Run {
12213            run_description: "Case insensitive subsequence and suffix matching",
12214            initial_state: "before EDiˇtoR after".into(),
12215            buffer_marked_text: "before <EDi|toR> after".into(),
12216            completion_label: "editor",
12217            completion_text: "editor",
12218            expected_with_insert_mode: "before editorˇtoR after".into(),
12219            expected_with_replace_mode: "before editorˇ after".into(),
12220            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
12221            expected_with_replace_suffix_mode: "before editorˇ after".into(),
12222        },
12223    ];
12224
12225    for run in runs {
12226        let run_variations = [
12227            (LspInsertMode::Insert, run.expected_with_insert_mode),
12228            (LspInsertMode::Replace, run.expected_with_replace_mode),
12229            (
12230                LspInsertMode::ReplaceSubsequence,
12231                run.expected_with_replace_subsequence_mode,
12232            ),
12233            (
12234                LspInsertMode::ReplaceSuffix,
12235                run.expected_with_replace_suffix_mode,
12236            ),
12237        ];
12238
12239        for (lsp_insert_mode, expected_text) in run_variations {
12240            eprintln!(
12241                "run = {:?}, mode = {lsp_insert_mode:.?}",
12242                run.run_description,
12243            );
12244
12245            update_test_language_settings(&mut cx, |settings| {
12246                settings.defaults.completions = Some(CompletionSettings {
12247                    lsp_insert_mode,
12248                    words: WordsCompletionMode::Disabled,
12249                    lsp: true,
12250                    lsp_fetch_timeout_ms: 0,
12251                });
12252            });
12253
12254            cx.set_state(&run.initial_state);
12255            cx.update_editor(|editor, window, cx| {
12256                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12257            });
12258
12259            let counter = Arc::new(AtomicUsize::new(0));
12260            handle_completion_request_with_insert_and_replace(
12261                &mut cx,
12262                &run.buffer_marked_text,
12263                vec![(run.completion_label, run.completion_text)],
12264                counter.clone(),
12265            )
12266            .await;
12267            cx.condition(|editor, _| editor.context_menu_visible())
12268                .await;
12269            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12270
12271            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12272                editor
12273                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
12274                    .unwrap()
12275            });
12276            cx.assert_editor_state(&expected_text);
12277            handle_resolve_completion_request(&mut cx, None).await;
12278            apply_additional_edits.await.unwrap();
12279        }
12280    }
12281}
12282
12283#[gpui::test]
12284async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
12285    init_test(cx, |_| {});
12286    let mut cx = EditorLspTestContext::new_rust(
12287        lsp::ServerCapabilities {
12288            completion_provider: Some(lsp::CompletionOptions {
12289                resolve_provider: Some(true),
12290                ..Default::default()
12291            }),
12292            ..Default::default()
12293        },
12294        cx,
12295    )
12296    .await;
12297
12298    let initial_state = "SubˇError";
12299    let buffer_marked_text = "<Sub|Error>";
12300    let completion_text = "SubscriptionError";
12301    let expected_with_insert_mode = "SubscriptionErrorˇError";
12302    let expected_with_replace_mode = "SubscriptionErrorˇ";
12303
12304    update_test_language_settings(&mut cx, |settings| {
12305        settings.defaults.completions = Some(CompletionSettings {
12306            words: WordsCompletionMode::Disabled,
12307            // set the opposite here to ensure that the action is overriding the default behavior
12308            lsp_insert_mode: LspInsertMode::Insert,
12309            lsp: true,
12310            lsp_fetch_timeout_ms: 0,
12311        });
12312    });
12313
12314    cx.set_state(initial_state);
12315    cx.update_editor(|editor, window, cx| {
12316        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12317    });
12318
12319    let counter = Arc::new(AtomicUsize::new(0));
12320    handle_completion_request_with_insert_and_replace(
12321        &mut cx,
12322        buffer_marked_text,
12323        vec![(completion_text, completion_text)],
12324        counter.clone(),
12325    )
12326    .await;
12327    cx.condition(|editor, _| editor.context_menu_visible())
12328        .await;
12329    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12330
12331    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12332        editor
12333            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12334            .unwrap()
12335    });
12336    cx.assert_editor_state(expected_with_replace_mode);
12337    handle_resolve_completion_request(&mut cx, None).await;
12338    apply_additional_edits.await.unwrap();
12339
12340    update_test_language_settings(&mut cx, |settings| {
12341        settings.defaults.completions = Some(CompletionSettings {
12342            words: WordsCompletionMode::Disabled,
12343            // set the opposite here to ensure that the action is overriding the default behavior
12344            lsp_insert_mode: LspInsertMode::Replace,
12345            lsp: true,
12346            lsp_fetch_timeout_ms: 0,
12347        });
12348    });
12349
12350    cx.set_state(initial_state);
12351    cx.update_editor(|editor, window, cx| {
12352        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12353    });
12354    handle_completion_request_with_insert_and_replace(
12355        &mut cx,
12356        buffer_marked_text,
12357        vec![(completion_text, completion_text)],
12358        counter.clone(),
12359    )
12360    .await;
12361    cx.condition(|editor, _| editor.context_menu_visible())
12362        .await;
12363    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12364
12365    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12366        editor
12367            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
12368            .unwrap()
12369    });
12370    cx.assert_editor_state(expected_with_insert_mode);
12371    handle_resolve_completion_request(&mut cx, None).await;
12372    apply_additional_edits.await.unwrap();
12373}
12374
12375#[gpui::test]
12376async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
12377    init_test(cx, |_| {});
12378    let mut cx = EditorLspTestContext::new_rust(
12379        lsp::ServerCapabilities {
12380            completion_provider: Some(lsp::CompletionOptions {
12381                resolve_provider: Some(true),
12382                ..Default::default()
12383            }),
12384            ..Default::default()
12385        },
12386        cx,
12387    )
12388    .await;
12389
12390    // scenario: surrounding text matches completion text
12391    let completion_text = "to_offset";
12392    let initial_state = indoc! {"
12393        1. buf.to_offˇsuffix
12394        2. buf.to_offˇsuf
12395        3. buf.to_offˇfix
12396        4. buf.to_offˇ
12397        5. into_offˇensive
12398        6. ˇsuffix
12399        7. let ˇ //
12400        8. aaˇzz
12401        9. buf.to_off«zzzzzˇ»suffix
12402        10. buf.«ˇzzzzz»suffix
12403        11. to_off«ˇzzzzz»
12404
12405        buf.to_offˇsuffix  // newest cursor
12406    "};
12407    let completion_marked_buffer = indoc! {"
12408        1. buf.to_offsuffix
12409        2. buf.to_offsuf
12410        3. buf.to_offfix
12411        4. buf.to_off
12412        5. into_offensive
12413        6. suffix
12414        7. let  //
12415        8. aazz
12416        9. buf.to_offzzzzzsuffix
12417        10. buf.zzzzzsuffix
12418        11. to_offzzzzz
12419
12420        buf.<to_off|suffix>  // newest cursor
12421    "};
12422    let expected = indoc! {"
12423        1. buf.to_offsetˇ
12424        2. buf.to_offsetˇsuf
12425        3. buf.to_offsetˇfix
12426        4. buf.to_offsetˇ
12427        5. into_offsetˇensive
12428        6. to_offsetˇsuffix
12429        7. let to_offsetˇ //
12430        8. aato_offsetˇzz
12431        9. buf.to_offsetˇ
12432        10. buf.to_offsetˇsuffix
12433        11. to_offsetˇ
12434
12435        buf.to_offsetˇ  // newest cursor
12436    "};
12437    cx.set_state(initial_state);
12438    cx.update_editor(|editor, window, cx| {
12439        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12440    });
12441    handle_completion_request_with_insert_and_replace(
12442        &mut cx,
12443        completion_marked_buffer,
12444        vec![(completion_text, completion_text)],
12445        Arc::new(AtomicUsize::new(0)),
12446    )
12447    .await;
12448    cx.condition(|editor, _| editor.context_menu_visible())
12449        .await;
12450    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12451        editor
12452            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12453            .unwrap()
12454    });
12455    cx.assert_editor_state(expected);
12456    handle_resolve_completion_request(&mut cx, None).await;
12457    apply_additional_edits.await.unwrap();
12458
12459    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
12460    let completion_text = "foo_and_bar";
12461    let initial_state = indoc! {"
12462        1. ooanbˇ
12463        2. zooanbˇ
12464        3. ooanbˇz
12465        4. zooanbˇz
12466        5. ooanˇ
12467        6. oanbˇ
12468
12469        ooanbˇ
12470    "};
12471    let completion_marked_buffer = indoc! {"
12472        1. ooanb
12473        2. zooanb
12474        3. ooanbz
12475        4. zooanbz
12476        5. ooan
12477        6. oanb
12478
12479        <ooanb|>
12480    "};
12481    let expected = indoc! {"
12482        1. foo_and_barˇ
12483        2. zfoo_and_barˇ
12484        3. foo_and_barˇz
12485        4. zfoo_and_barˇz
12486        5. ooanfoo_and_barˇ
12487        6. oanbfoo_and_barˇ
12488
12489        foo_and_barˇ
12490    "};
12491    cx.set_state(initial_state);
12492    cx.update_editor(|editor, window, cx| {
12493        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12494    });
12495    handle_completion_request_with_insert_and_replace(
12496        &mut cx,
12497        completion_marked_buffer,
12498        vec![(completion_text, completion_text)],
12499        Arc::new(AtomicUsize::new(0)),
12500    )
12501    .await;
12502    cx.condition(|editor, _| editor.context_menu_visible())
12503        .await;
12504    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12505        editor
12506            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12507            .unwrap()
12508    });
12509    cx.assert_editor_state(expected);
12510    handle_resolve_completion_request(&mut cx, None).await;
12511    apply_additional_edits.await.unwrap();
12512
12513    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
12514    // (expects the same as if it was inserted at the end)
12515    let completion_text = "foo_and_bar";
12516    let initial_state = indoc! {"
12517        1. ooˇanb
12518        2. zooˇanb
12519        3. ooˇanbz
12520        4. zooˇanbz
12521
12522        ooˇanb
12523    "};
12524    let completion_marked_buffer = indoc! {"
12525        1. ooanb
12526        2. zooanb
12527        3. ooanbz
12528        4. zooanbz
12529
12530        <oo|anb>
12531    "};
12532    let expected = indoc! {"
12533        1. foo_and_barˇ
12534        2. zfoo_and_barˇ
12535        3. foo_and_barˇz
12536        4. zfoo_and_barˇz
12537
12538        foo_and_barˇ
12539    "};
12540    cx.set_state(initial_state);
12541    cx.update_editor(|editor, window, cx| {
12542        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12543    });
12544    handle_completion_request_with_insert_and_replace(
12545        &mut cx,
12546        completion_marked_buffer,
12547        vec![(completion_text, completion_text)],
12548        Arc::new(AtomicUsize::new(0)),
12549    )
12550    .await;
12551    cx.condition(|editor, _| editor.context_menu_visible())
12552        .await;
12553    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12554        editor
12555            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12556            .unwrap()
12557    });
12558    cx.assert_editor_state(expected);
12559    handle_resolve_completion_request(&mut cx, None).await;
12560    apply_additional_edits.await.unwrap();
12561}
12562
12563// This used to crash
12564#[gpui::test]
12565async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
12566    init_test(cx, |_| {});
12567
12568    let buffer_text = indoc! {"
12569        fn main() {
12570            10.satu;
12571
12572            //
12573            // separate cursors so they open in different excerpts (manually reproducible)
12574            //
12575
12576            10.satu20;
12577        }
12578    "};
12579    let multibuffer_text_with_selections = indoc! {"
12580        fn main() {
12581            10.satuˇ;
12582
12583            //
12584
12585            //
12586
12587            10.satuˇ20;
12588        }
12589    "};
12590    let expected_multibuffer = indoc! {"
12591        fn main() {
12592            10.saturating_sub()ˇ;
12593
12594            //
12595
12596            //
12597
12598            10.saturating_sub()ˇ;
12599        }
12600    "};
12601
12602    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
12603    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
12604
12605    let fs = FakeFs::new(cx.executor());
12606    fs.insert_tree(
12607        path!("/a"),
12608        json!({
12609            "main.rs": buffer_text,
12610        }),
12611    )
12612    .await;
12613
12614    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12615    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12616    language_registry.add(rust_lang());
12617    let mut fake_servers = language_registry.register_fake_lsp(
12618        "Rust",
12619        FakeLspAdapter {
12620            capabilities: lsp::ServerCapabilities {
12621                completion_provider: Some(lsp::CompletionOptions {
12622                    resolve_provider: None,
12623                    ..lsp::CompletionOptions::default()
12624                }),
12625                ..lsp::ServerCapabilities::default()
12626            },
12627            ..FakeLspAdapter::default()
12628        },
12629    );
12630    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12631    let cx = &mut VisualTestContext::from_window(*workspace, cx);
12632    let buffer = project
12633        .update(cx, |project, cx| {
12634            project.open_local_buffer(path!("/a/main.rs"), cx)
12635        })
12636        .await
12637        .unwrap();
12638
12639    let multi_buffer = cx.new(|cx| {
12640        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
12641        multi_buffer.push_excerpts(
12642            buffer.clone(),
12643            [ExcerptRange::new(0..first_excerpt_end)],
12644            cx,
12645        );
12646        multi_buffer.push_excerpts(
12647            buffer.clone(),
12648            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
12649            cx,
12650        );
12651        multi_buffer
12652    });
12653
12654    let editor = workspace
12655        .update(cx, |_, window, cx| {
12656            cx.new(|cx| {
12657                Editor::new(
12658                    EditorMode::Full {
12659                        scale_ui_elements_with_buffer_font_size: false,
12660                        show_active_line_background: false,
12661                        sized_by_content: false,
12662                    },
12663                    multi_buffer.clone(),
12664                    Some(project.clone()),
12665                    window,
12666                    cx,
12667                )
12668            })
12669        })
12670        .unwrap();
12671
12672    let pane = workspace
12673        .update(cx, |workspace, _, _| workspace.active_pane().clone())
12674        .unwrap();
12675    pane.update_in(cx, |pane, window, cx| {
12676        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
12677    });
12678
12679    let fake_server = fake_servers.next().await.unwrap();
12680
12681    editor.update_in(cx, |editor, window, cx| {
12682        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12683            s.select_ranges([
12684                Point::new(1, 11)..Point::new(1, 11),
12685                Point::new(7, 11)..Point::new(7, 11),
12686            ])
12687        });
12688
12689        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
12690    });
12691
12692    editor.update_in(cx, |editor, window, cx| {
12693        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12694    });
12695
12696    fake_server
12697        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
12698            let completion_item = lsp::CompletionItem {
12699                label: "saturating_sub()".into(),
12700                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
12701                    lsp::InsertReplaceEdit {
12702                        new_text: "saturating_sub()".to_owned(),
12703                        insert: lsp::Range::new(
12704                            lsp::Position::new(7, 7),
12705                            lsp::Position::new(7, 11),
12706                        ),
12707                        replace: lsp::Range::new(
12708                            lsp::Position::new(7, 7),
12709                            lsp::Position::new(7, 13),
12710                        ),
12711                    },
12712                )),
12713                ..lsp::CompletionItem::default()
12714            };
12715
12716            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
12717        })
12718        .next()
12719        .await
12720        .unwrap();
12721
12722    cx.condition(&editor, |editor, _| editor.context_menu_visible())
12723        .await;
12724
12725    editor
12726        .update_in(cx, |editor, window, cx| {
12727            editor
12728                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12729                .unwrap()
12730        })
12731        .await
12732        .unwrap();
12733
12734    editor.update(cx, |editor, cx| {
12735        assert_text_with_selections(editor, expected_multibuffer, cx);
12736    })
12737}
12738
12739#[gpui::test]
12740async fn test_completion(cx: &mut TestAppContext) {
12741    init_test(cx, |_| {});
12742
12743    let mut cx = EditorLspTestContext::new_rust(
12744        lsp::ServerCapabilities {
12745            completion_provider: Some(lsp::CompletionOptions {
12746                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12747                resolve_provider: Some(true),
12748                ..Default::default()
12749            }),
12750            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12751            ..Default::default()
12752        },
12753        cx,
12754    )
12755    .await;
12756    let counter = Arc::new(AtomicUsize::new(0));
12757
12758    cx.set_state(indoc! {"
12759        oneˇ
12760        two
12761        three
12762    "});
12763    cx.simulate_keystroke(".");
12764    handle_completion_request(
12765        indoc! {"
12766            one.|<>
12767            two
12768            three
12769        "},
12770        vec!["first_completion", "second_completion"],
12771        true,
12772        counter.clone(),
12773        &mut cx,
12774    )
12775    .await;
12776    cx.condition(|editor, _| editor.context_menu_visible())
12777        .await;
12778    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12779
12780    let _handler = handle_signature_help_request(
12781        &mut cx,
12782        lsp::SignatureHelp {
12783            signatures: vec![lsp::SignatureInformation {
12784                label: "test signature".to_string(),
12785                documentation: None,
12786                parameters: Some(vec![lsp::ParameterInformation {
12787                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
12788                    documentation: None,
12789                }]),
12790                active_parameter: None,
12791            }],
12792            active_signature: None,
12793            active_parameter: None,
12794        },
12795    );
12796    cx.update_editor(|editor, window, cx| {
12797        assert!(
12798            !editor.signature_help_state.is_shown(),
12799            "No signature help was called for"
12800        );
12801        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12802    });
12803    cx.run_until_parked();
12804    cx.update_editor(|editor, _, _| {
12805        assert!(
12806            !editor.signature_help_state.is_shown(),
12807            "No signature help should be shown when completions menu is open"
12808        );
12809    });
12810
12811    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12812        editor.context_menu_next(&Default::default(), window, cx);
12813        editor
12814            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12815            .unwrap()
12816    });
12817    cx.assert_editor_state(indoc! {"
12818        one.second_completionˇ
12819        two
12820        three
12821    "});
12822
12823    handle_resolve_completion_request(
12824        &mut cx,
12825        Some(vec![
12826            (
12827                //This overlaps with the primary completion edit which is
12828                //misbehavior from the LSP spec, test that we filter it out
12829                indoc! {"
12830                    one.second_ˇcompletion
12831                    two
12832                    threeˇ
12833                "},
12834                "overlapping additional edit",
12835            ),
12836            (
12837                indoc! {"
12838                    one.second_completion
12839                    two
12840                    threeˇ
12841                "},
12842                "\nadditional edit",
12843            ),
12844        ]),
12845    )
12846    .await;
12847    apply_additional_edits.await.unwrap();
12848    cx.assert_editor_state(indoc! {"
12849        one.second_completionˇ
12850        two
12851        three
12852        additional edit
12853    "});
12854
12855    cx.set_state(indoc! {"
12856        one.second_completion
12857        twoˇ
12858        threeˇ
12859        additional edit
12860    "});
12861    cx.simulate_keystroke(" ");
12862    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12863    cx.simulate_keystroke("s");
12864    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12865
12866    cx.assert_editor_state(indoc! {"
12867        one.second_completion
12868        two sˇ
12869        three sˇ
12870        additional edit
12871    "});
12872    handle_completion_request(
12873        indoc! {"
12874            one.second_completion
12875            two s
12876            three <s|>
12877            additional edit
12878        "},
12879        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12880        true,
12881        counter.clone(),
12882        &mut cx,
12883    )
12884    .await;
12885    cx.condition(|editor, _| editor.context_menu_visible())
12886        .await;
12887    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12888
12889    cx.simulate_keystroke("i");
12890
12891    handle_completion_request(
12892        indoc! {"
12893            one.second_completion
12894            two si
12895            three <si|>
12896            additional edit
12897        "},
12898        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12899        true,
12900        counter.clone(),
12901        &mut cx,
12902    )
12903    .await;
12904    cx.condition(|editor, _| editor.context_menu_visible())
12905        .await;
12906    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12907
12908    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12909        editor
12910            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12911            .unwrap()
12912    });
12913    cx.assert_editor_state(indoc! {"
12914        one.second_completion
12915        two sixth_completionˇ
12916        three sixth_completionˇ
12917        additional edit
12918    "});
12919
12920    apply_additional_edits.await.unwrap();
12921
12922    update_test_language_settings(&mut cx, |settings| {
12923        settings.defaults.show_completions_on_input = Some(false);
12924    });
12925    cx.set_state("editorˇ");
12926    cx.simulate_keystroke(".");
12927    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12928    cx.simulate_keystrokes("c l o");
12929    cx.assert_editor_state("editor.cloˇ");
12930    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12931    cx.update_editor(|editor, window, cx| {
12932        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12933    });
12934    handle_completion_request(
12935        "editor.<clo|>",
12936        vec!["close", "clobber"],
12937        true,
12938        counter.clone(),
12939        &mut cx,
12940    )
12941    .await;
12942    cx.condition(|editor, _| editor.context_menu_visible())
12943        .await;
12944    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
12945
12946    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12947        editor
12948            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12949            .unwrap()
12950    });
12951    cx.assert_editor_state("editor.clobberˇ");
12952    handle_resolve_completion_request(&mut cx, None).await;
12953    apply_additional_edits.await.unwrap();
12954}
12955
12956#[gpui::test]
12957async fn test_completion_reuse(cx: &mut TestAppContext) {
12958    init_test(cx, |_| {});
12959
12960    let mut cx = EditorLspTestContext::new_rust(
12961        lsp::ServerCapabilities {
12962            completion_provider: Some(lsp::CompletionOptions {
12963                trigger_characters: Some(vec![".".to_string()]),
12964                ..Default::default()
12965            }),
12966            ..Default::default()
12967        },
12968        cx,
12969    )
12970    .await;
12971
12972    let counter = Arc::new(AtomicUsize::new(0));
12973    cx.set_state("objˇ");
12974    cx.simulate_keystroke(".");
12975
12976    // Initial completion request returns complete results
12977    let is_incomplete = false;
12978    handle_completion_request(
12979        "obj.|<>",
12980        vec!["a", "ab", "abc"],
12981        is_incomplete,
12982        counter.clone(),
12983        &mut cx,
12984    )
12985    .await;
12986    cx.run_until_parked();
12987    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12988    cx.assert_editor_state("obj.ˇ");
12989    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12990
12991    // Type "a" - filters existing completions
12992    cx.simulate_keystroke("a");
12993    cx.run_until_parked();
12994    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12995    cx.assert_editor_state("obj.aˇ");
12996    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12997
12998    // Type "b" - filters existing completions
12999    cx.simulate_keystroke("b");
13000    cx.run_until_parked();
13001    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13002    cx.assert_editor_state("obj.abˇ");
13003    check_displayed_completions(vec!["ab", "abc"], &mut cx);
13004
13005    // Type "c" - filters existing completions
13006    cx.simulate_keystroke("c");
13007    cx.run_until_parked();
13008    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13009    cx.assert_editor_state("obj.abcˇ");
13010    check_displayed_completions(vec!["abc"], &mut cx);
13011
13012    // Backspace to delete "c" - filters existing completions
13013    cx.update_editor(|editor, window, cx| {
13014        editor.backspace(&Backspace, window, cx);
13015    });
13016    cx.run_until_parked();
13017    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13018    cx.assert_editor_state("obj.abˇ");
13019    check_displayed_completions(vec!["ab", "abc"], &mut cx);
13020
13021    // Moving cursor to the left dismisses menu.
13022    cx.update_editor(|editor, window, cx| {
13023        editor.move_left(&MoveLeft, window, cx);
13024    });
13025    cx.run_until_parked();
13026    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13027    cx.assert_editor_state("obj.aˇb");
13028    cx.update_editor(|editor, _, _| {
13029        assert_eq!(editor.context_menu_visible(), false);
13030    });
13031
13032    // Type "b" - new request
13033    cx.simulate_keystroke("b");
13034    let is_incomplete = false;
13035    handle_completion_request(
13036        "obj.<ab|>a",
13037        vec!["ab", "abc"],
13038        is_incomplete,
13039        counter.clone(),
13040        &mut cx,
13041    )
13042    .await;
13043    cx.run_until_parked();
13044    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13045    cx.assert_editor_state("obj.abˇb");
13046    check_displayed_completions(vec!["ab", "abc"], &mut cx);
13047
13048    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
13049    cx.update_editor(|editor, window, cx| {
13050        editor.backspace(&Backspace, window, cx);
13051    });
13052    let is_incomplete = false;
13053    handle_completion_request(
13054        "obj.<a|>b",
13055        vec!["a", "ab", "abc"],
13056        is_incomplete,
13057        counter.clone(),
13058        &mut cx,
13059    )
13060    .await;
13061    cx.run_until_parked();
13062    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
13063    cx.assert_editor_state("obj.aˇb");
13064    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
13065
13066    // Backspace to delete "a" - dismisses menu.
13067    cx.update_editor(|editor, window, cx| {
13068        editor.backspace(&Backspace, window, cx);
13069    });
13070    cx.run_until_parked();
13071    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
13072    cx.assert_editor_state("obj.ˇb");
13073    cx.update_editor(|editor, _, _| {
13074        assert_eq!(editor.context_menu_visible(), false);
13075    });
13076}
13077
13078#[gpui::test]
13079async fn test_word_completion(cx: &mut TestAppContext) {
13080    let lsp_fetch_timeout_ms = 10;
13081    init_test(cx, |language_settings| {
13082        language_settings.defaults.completions = Some(CompletionSettings {
13083            words: WordsCompletionMode::Fallback,
13084            lsp: true,
13085            lsp_fetch_timeout_ms: 10,
13086            lsp_insert_mode: LspInsertMode::Insert,
13087        });
13088    });
13089
13090    let mut cx = EditorLspTestContext::new_rust(
13091        lsp::ServerCapabilities {
13092            completion_provider: Some(lsp::CompletionOptions {
13093                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13094                ..lsp::CompletionOptions::default()
13095            }),
13096            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13097            ..lsp::ServerCapabilities::default()
13098        },
13099        cx,
13100    )
13101    .await;
13102
13103    let throttle_completions = Arc::new(AtomicBool::new(false));
13104
13105    let lsp_throttle_completions = throttle_completions.clone();
13106    let _completion_requests_handler =
13107        cx.lsp
13108            .server
13109            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
13110                let lsp_throttle_completions = lsp_throttle_completions.clone();
13111                let cx = cx.clone();
13112                async move {
13113                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
13114                        cx.background_executor()
13115                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
13116                            .await;
13117                    }
13118                    Ok(Some(lsp::CompletionResponse::Array(vec![
13119                        lsp::CompletionItem {
13120                            label: "first".into(),
13121                            ..lsp::CompletionItem::default()
13122                        },
13123                        lsp::CompletionItem {
13124                            label: "last".into(),
13125                            ..lsp::CompletionItem::default()
13126                        },
13127                    ])))
13128                }
13129            });
13130
13131    cx.set_state(indoc! {"
13132        oneˇ
13133        two
13134        three
13135    "});
13136    cx.simulate_keystroke(".");
13137    cx.executor().run_until_parked();
13138    cx.condition(|editor, _| editor.context_menu_visible())
13139        .await;
13140    cx.update_editor(|editor, window, cx| {
13141        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13142        {
13143            assert_eq!(
13144                completion_menu_entries(menu),
13145                &["first", "last"],
13146                "When LSP server is fast to reply, no fallback word completions are used"
13147            );
13148        } else {
13149            panic!("expected completion menu to be open");
13150        }
13151        editor.cancel(&Cancel, window, cx);
13152    });
13153    cx.executor().run_until_parked();
13154    cx.condition(|editor, _| !editor.context_menu_visible())
13155        .await;
13156
13157    throttle_completions.store(true, atomic::Ordering::Release);
13158    cx.simulate_keystroke(".");
13159    cx.executor()
13160        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
13161    cx.executor().run_until_parked();
13162    cx.condition(|editor, _| editor.context_menu_visible())
13163        .await;
13164    cx.update_editor(|editor, _, _| {
13165        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13166        {
13167            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
13168                "When LSP server is slow, document words can be shown instead, if configured accordingly");
13169        } else {
13170            panic!("expected completion menu to be open");
13171        }
13172    });
13173}
13174
13175#[gpui::test]
13176async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
13177    init_test(cx, |language_settings| {
13178        language_settings.defaults.completions = Some(CompletionSettings {
13179            words: WordsCompletionMode::Enabled,
13180            lsp: true,
13181            lsp_fetch_timeout_ms: 0,
13182            lsp_insert_mode: LspInsertMode::Insert,
13183        });
13184    });
13185
13186    let mut cx = EditorLspTestContext::new_rust(
13187        lsp::ServerCapabilities {
13188            completion_provider: Some(lsp::CompletionOptions {
13189                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13190                ..lsp::CompletionOptions::default()
13191            }),
13192            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13193            ..lsp::ServerCapabilities::default()
13194        },
13195        cx,
13196    )
13197    .await;
13198
13199    let _completion_requests_handler =
13200        cx.lsp
13201            .server
13202            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
13203                Ok(Some(lsp::CompletionResponse::Array(vec![
13204                    lsp::CompletionItem {
13205                        label: "first".into(),
13206                        ..lsp::CompletionItem::default()
13207                    },
13208                    lsp::CompletionItem {
13209                        label: "last".into(),
13210                        ..lsp::CompletionItem::default()
13211                    },
13212                ])))
13213            });
13214
13215    cx.set_state(indoc! {"ˇ
13216        first
13217        last
13218        second
13219    "});
13220    cx.simulate_keystroke(".");
13221    cx.executor().run_until_parked();
13222    cx.condition(|editor, _| editor.context_menu_visible())
13223        .await;
13224    cx.update_editor(|editor, _, _| {
13225        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13226        {
13227            assert_eq!(
13228                completion_menu_entries(menu),
13229                &["first", "last", "second"],
13230                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
13231            );
13232        } else {
13233            panic!("expected completion menu to be open");
13234        }
13235    });
13236}
13237
13238#[gpui::test]
13239async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
13240    init_test(cx, |language_settings| {
13241        language_settings.defaults.completions = Some(CompletionSettings {
13242            words: WordsCompletionMode::Disabled,
13243            lsp: true,
13244            lsp_fetch_timeout_ms: 0,
13245            lsp_insert_mode: LspInsertMode::Insert,
13246        });
13247    });
13248
13249    let mut cx = EditorLspTestContext::new_rust(
13250        lsp::ServerCapabilities {
13251            completion_provider: Some(lsp::CompletionOptions {
13252                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13253                ..lsp::CompletionOptions::default()
13254            }),
13255            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13256            ..lsp::ServerCapabilities::default()
13257        },
13258        cx,
13259    )
13260    .await;
13261
13262    let _completion_requests_handler =
13263        cx.lsp
13264            .server
13265            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
13266                panic!("LSP completions should not be queried when dealing with word completions")
13267            });
13268
13269    cx.set_state(indoc! {"ˇ
13270        first
13271        last
13272        second
13273    "});
13274    cx.update_editor(|editor, window, cx| {
13275        editor.show_word_completions(&ShowWordCompletions, window, cx);
13276    });
13277    cx.executor().run_until_parked();
13278    cx.condition(|editor, _| editor.context_menu_visible())
13279        .await;
13280    cx.update_editor(|editor, _, _| {
13281        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13282        {
13283            assert_eq!(
13284                completion_menu_entries(menu),
13285                &["first", "last", "second"],
13286                "`ShowWordCompletions` action should show word completions"
13287            );
13288        } else {
13289            panic!("expected completion menu to be open");
13290        }
13291    });
13292
13293    cx.simulate_keystroke("l");
13294    cx.executor().run_until_parked();
13295    cx.condition(|editor, _| editor.context_menu_visible())
13296        .await;
13297    cx.update_editor(|editor, _, _| {
13298        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13299        {
13300            assert_eq!(
13301                completion_menu_entries(menu),
13302                &["last"],
13303                "After showing word completions, further editing should filter them and not query the LSP"
13304            );
13305        } else {
13306            panic!("expected completion menu to be open");
13307        }
13308    });
13309}
13310
13311#[gpui::test]
13312async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
13313    init_test(cx, |language_settings| {
13314        language_settings.defaults.completions = Some(CompletionSettings {
13315            words: WordsCompletionMode::Fallback,
13316            lsp: false,
13317            lsp_fetch_timeout_ms: 0,
13318            lsp_insert_mode: LspInsertMode::Insert,
13319        });
13320    });
13321
13322    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
13323
13324    cx.set_state(indoc! {"ˇ
13325        0_usize
13326        let
13327        33
13328        4.5f32
13329    "});
13330    cx.update_editor(|editor, window, cx| {
13331        editor.show_completions(&ShowCompletions::default(), window, cx);
13332    });
13333    cx.executor().run_until_parked();
13334    cx.condition(|editor, _| editor.context_menu_visible())
13335        .await;
13336    cx.update_editor(|editor, window, cx| {
13337        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13338        {
13339            assert_eq!(
13340                completion_menu_entries(menu),
13341                &["let"],
13342                "With no digits in the completion query, no digits should be in the word completions"
13343            );
13344        } else {
13345            panic!("expected completion menu to be open");
13346        }
13347        editor.cancel(&Cancel, window, cx);
13348    });
13349
13350    cx.set_state(indoc! {"13351        0_usize
13352        let
13353        3
13354        33.35f32
13355    "});
13356    cx.update_editor(|editor, window, cx| {
13357        editor.show_completions(&ShowCompletions::default(), window, cx);
13358    });
13359    cx.executor().run_until_parked();
13360    cx.condition(|editor, _| editor.context_menu_visible())
13361        .await;
13362    cx.update_editor(|editor, _, _| {
13363        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13364        {
13365            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
13366                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
13367        } else {
13368            panic!("expected completion menu to be open");
13369        }
13370    });
13371}
13372
13373fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
13374    let position = || lsp::Position {
13375        line: params.text_document_position.position.line,
13376        character: params.text_document_position.position.character,
13377    };
13378    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13379        range: lsp::Range {
13380            start: position(),
13381            end: position(),
13382        },
13383        new_text: text.to_string(),
13384    }))
13385}
13386
13387#[gpui::test]
13388async fn test_multiline_completion(cx: &mut TestAppContext) {
13389    init_test(cx, |_| {});
13390
13391    let fs = FakeFs::new(cx.executor());
13392    fs.insert_tree(
13393        path!("/a"),
13394        json!({
13395            "main.ts": "a",
13396        }),
13397    )
13398    .await;
13399
13400    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13401    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13402    let typescript_language = Arc::new(Language::new(
13403        LanguageConfig {
13404            name: "TypeScript".into(),
13405            matcher: LanguageMatcher {
13406                path_suffixes: vec!["ts".to_string()],
13407                ..LanguageMatcher::default()
13408            },
13409            line_comments: vec!["// ".into()],
13410            ..LanguageConfig::default()
13411        },
13412        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
13413    ));
13414    language_registry.add(typescript_language.clone());
13415    let mut fake_servers = language_registry.register_fake_lsp(
13416        "TypeScript",
13417        FakeLspAdapter {
13418            capabilities: lsp::ServerCapabilities {
13419                completion_provider: Some(lsp::CompletionOptions {
13420                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13421                    ..lsp::CompletionOptions::default()
13422                }),
13423                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13424                ..lsp::ServerCapabilities::default()
13425            },
13426            // Emulate vtsls label generation
13427            label_for_completion: Some(Box::new(|item, _| {
13428                let text = if let Some(description) = item
13429                    .label_details
13430                    .as_ref()
13431                    .and_then(|label_details| label_details.description.as_ref())
13432                {
13433                    format!("{} {}", item.label, description)
13434                } else if let Some(detail) = &item.detail {
13435                    format!("{} {}", item.label, detail)
13436                } else {
13437                    item.label.clone()
13438                };
13439                let len = text.len();
13440                Some(language::CodeLabel {
13441                    text,
13442                    runs: Vec::new(),
13443                    filter_range: 0..len,
13444                })
13445            })),
13446            ..FakeLspAdapter::default()
13447        },
13448    );
13449    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13450    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13451    let worktree_id = workspace
13452        .update(cx, |workspace, _window, cx| {
13453            workspace.project().update(cx, |project, cx| {
13454                project.worktrees(cx).next().unwrap().read(cx).id()
13455            })
13456        })
13457        .unwrap();
13458    let _buffer = project
13459        .update(cx, |project, cx| {
13460            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
13461        })
13462        .await
13463        .unwrap();
13464    let editor = workspace
13465        .update(cx, |workspace, window, cx| {
13466            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
13467        })
13468        .unwrap()
13469        .await
13470        .unwrap()
13471        .downcast::<Editor>()
13472        .unwrap();
13473    let fake_server = fake_servers.next().await.unwrap();
13474
13475    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
13476    let multiline_label_2 = "a\nb\nc\n";
13477    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
13478    let multiline_description = "d\ne\nf\n";
13479    let multiline_detail_2 = "g\nh\ni\n";
13480
13481    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
13482        move |params, _| async move {
13483            Ok(Some(lsp::CompletionResponse::Array(vec![
13484                lsp::CompletionItem {
13485                    label: multiline_label.to_string(),
13486                    text_edit: gen_text_edit(&params, "new_text_1"),
13487                    ..lsp::CompletionItem::default()
13488                },
13489                lsp::CompletionItem {
13490                    label: "single line label 1".to_string(),
13491                    detail: Some(multiline_detail.to_string()),
13492                    text_edit: gen_text_edit(&params, "new_text_2"),
13493                    ..lsp::CompletionItem::default()
13494                },
13495                lsp::CompletionItem {
13496                    label: "single line label 2".to_string(),
13497                    label_details: Some(lsp::CompletionItemLabelDetails {
13498                        description: Some(multiline_description.to_string()),
13499                        detail: None,
13500                    }),
13501                    text_edit: gen_text_edit(&params, "new_text_2"),
13502                    ..lsp::CompletionItem::default()
13503                },
13504                lsp::CompletionItem {
13505                    label: multiline_label_2.to_string(),
13506                    detail: Some(multiline_detail_2.to_string()),
13507                    text_edit: gen_text_edit(&params, "new_text_3"),
13508                    ..lsp::CompletionItem::default()
13509                },
13510                lsp::CompletionItem {
13511                    label: "Label with many     spaces and \t but without newlines".to_string(),
13512                    detail: Some(
13513                        "Details with many     spaces and \t but without newlines".to_string(),
13514                    ),
13515                    text_edit: gen_text_edit(&params, "new_text_4"),
13516                    ..lsp::CompletionItem::default()
13517                },
13518            ])))
13519        },
13520    );
13521
13522    editor.update_in(cx, |editor, window, cx| {
13523        cx.focus_self(window);
13524        editor.move_to_end(&MoveToEnd, window, cx);
13525        editor.handle_input(".", window, cx);
13526    });
13527    cx.run_until_parked();
13528    completion_handle.next().await.unwrap();
13529
13530    editor.update(cx, |editor, _| {
13531        assert!(editor.context_menu_visible());
13532        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13533        {
13534            let completion_labels = menu
13535                .completions
13536                .borrow()
13537                .iter()
13538                .map(|c| c.label.text.clone())
13539                .collect::<Vec<_>>();
13540            assert_eq!(
13541                completion_labels,
13542                &[
13543                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
13544                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
13545                    "single line label 2 d e f ",
13546                    "a b c g h i ",
13547                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
13548                ],
13549                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
13550            );
13551
13552            for completion in menu
13553                .completions
13554                .borrow()
13555                .iter() {
13556                    assert_eq!(
13557                        completion.label.filter_range,
13558                        0..completion.label.text.len(),
13559                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
13560                    );
13561                }
13562        } else {
13563            panic!("expected completion menu to be open");
13564        }
13565    });
13566}
13567
13568#[gpui::test]
13569async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
13570    init_test(cx, |_| {});
13571    let mut cx = EditorLspTestContext::new_rust(
13572        lsp::ServerCapabilities {
13573            completion_provider: Some(lsp::CompletionOptions {
13574                trigger_characters: Some(vec![".".to_string()]),
13575                ..Default::default()
13576            }),
13577            ..Default::default()
13578        },
13579        cx,
13580    )
13581    .await;
13582    cx.lsp
13583        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13584            Ok(Some(lsp::CompletionResponse::Array(vec![
13585                lsp::CompletionItem {
13586                    label: "first".into(),
13587                    ..Default::default()
13588                },
13589                lsp::CompletionItem {
13590                    label: "last".into(),
13591                    ..Default::default()
13592                },
13593            ])))
13594        });
13595    cx.set_state("variableˇ");
13596    cx.simulate_keystroke(".");
13597    cx.executor().run_until_parked();
13598
13599    cx.update_editor(|editor, _, _| {
13600        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13601        {
13602            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
13603        } else {
13604            panic!("expected completion menu to be open");
13605        }
13606    });
13607
13608    cx.update_editor(|editor, window, cx| {
13609        editor.move_page_down(&MovePageDown::default(), window, cx);
13610        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13611        {
13612            assert!(
13613                menu.selected_item == 1,
13614                "expected PageDown to select the last item from the context menu"
13615            );
13616        } else {
13617            panic!("expected completion menu to stay open after PageDown");
13618        }
13619    });
13620
13621    cx.update_editor(|editor, window, cx| {
13622        editor.move_page_up(&MovePageUp::default(), window, cx);
13623        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13624        {
13625            assert!(
13626                menu.selected_item == 0,
13627                "expected PageUp to select the first item from the context menu"
13628            );
13629        } else {
13630            panic!("expected completion menu to stay open after PageUp");
13631        }
13632    });
13633}
13634
13635#[gpui::test]
13636async fn test_as_is_completions(cx: &mut TestAppContext) {
13637    init_test(cx, |_| {});
13638    let mut cx = EditorLspTestContext::new_rust(
13639        lsp::ServerCapabilities {
13640            completion_provider: Some(lsp::CompletionOptions {
13641                ..Default::default()
13642            }),
13643            ..Default::default()
13644        },
13645        cx,
13646    )
13647    .await;
13648    cx.lsp
13649        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13650            Ok(Some(lsp::CompletionResponse::Array(vec![
13651                lsp::CompletionItem {
13652                    label: "unsafe".into(),
13653                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13654                        range: lsp::Range {
13655                            start: lsp::Position {
13656                                line: 1,
13657                                character: 2,
13658                            },
13659                            end: lsp::Position {
13660                                line: 1,
13661                                character: 3,
13662                            },
13663                        },
13664                        new_text: "unsafe".to_string(),
13665                    })),
13666                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
13667                    ..Default::default()
13668                },
13669            ])))
13670        });
13671    cx.set_state("fn a() {}\n");
13672    cx.executor().run_until_parked();
13673    cx.update_editor(|editor, window, cx| {
13674        editor.show_completions(
13675            &ShowCompletions {
13676                trigger: Some("\n".into()),
13677            },
13678            window,
13679            cx,
13680        );
13681    });
13682    cx.executor().run_until_parked();
13683
13684    cx.update_editor(|editor, window, cx| {
13685        editor.confirm_completion(&Default::default(), window, cx)
13686    });
13687    cx.executor().run_until_parked();
13688    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
13689}
13690
13691#[gpui::test]
13692async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
13693    init_test(cx, |_| {});
13694    let language =
13695        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
13696    let mut cx = EditorLspTestContext::new(
13697        language,
13698        lsp::ServerCapabilities {
13699            completion_provider: Some(lsp::CompletionOptions {
13700                ..lsp::CompletionOptions::default()
13701            }),
13702            ..lsp::ServerCapabilities::default()
13703        },
13704        cx,
13705    )
13706    .await;
13707
13708    cx.set_state(
13709        "#ifndef BAR_H
13710#define BAR_H
13711
13712#include <stdbool.h>
13713
13714int fn_branch(bool do_branch1, bool do_branch2);
13715
13716#endif // BAR_H
13717ˇ",
13718    );
13719    cx.executor().run_until_parked();
13720    cx.update_editor(|editor, window, cx| {
13721        editor.handle_input("#", window, cx);
13722    });
13723    cx.executor().run_until_parked();
13724    cx.update_editor(|editor, window, cx| {
13725        editor.handle_input("i", window, cx);
13726    });
13727    cx.executor().run_until_parked();
13728    cx.update_editor(|editor, window, cx| {
13729        editor.handle_input("n", window, cx);
13730    });
13731    cx.executor().run_until_parked();
13732    cx.assert_editor_state(
13733        "#ifndef BAR_H
13734#define BAR_H
13735
13736#include <stdbool.h>
13737
13738int fn_branch(bool do_branch1, bool do_branch2);
13739
13740#endif // BAR_H
13741#inˇ",
13742    );
13743
13744    cx.lsp
13745        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13746            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
13747                is_incomplete: false,
13748                item_defaults: None,
13749                items: vec![lsp::CompletionItem {
13750                    kind: Some(lsp::CompletionItemKind::SNIPPET),
13751                    label_details: Some(lsp::CompletionItemLabelDetails {
13752                        detail: Some("header".to_string()),
13753                        description: None,
13754                    }),
13755                    label: " include".to_string(),
13756                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13757                        range: lsp::Range {
13758                            start: lsp::Position {
13759                                line: 8,
13760                                character: 1,
13761                            },
13762                            end: lsp::Position {
13763                                line: 8,
13764                                character: 1,
13765                            },
13766                        },
13767                        new_text: "include \"$0\"".to_string(),
13768                    })),
13769                    sort_text: Some("40b67681include".to_string()),
13770                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
13771                    filter_text: Some("include".to_string()),
13772                    insert_text: Some("include \"$0\"".to_string()),
13773                    ..lsp::CompletionItem::default()
13774                }],
13775            })))
13776        });
13777    cx.update_editor(|editor, window, cx| {
13778        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13779    });
13780    cx.executor().run_until_parked();
13781    cx.update_editor(|editor, window, cx| {
13782        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
13783    });
13784    cx.executor().run_until_parked();
13785    cx.assert_editor_state(
13786        "#ifndef BAR_H
13787#define BAR_H
13788
13789#include <stdbool.h>
13790
13791int fn_branch(bool do_branch1, bool do_branch2);
13792
13793#endif // BAR_H
13794#include \"ˇ\"",
13795    );
13796
13797    cx.lsp
13798        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13799            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
13800                is_incomplete: true,
13801                item_defaults: None,
13802                items: vec![lsp::CompletionItem {
13803                    kind: Some(lsp::CompletionItemKind::FILE),
13804                    label: "AGL/".to_string(),
13805                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13806                        range: lsp::Range {
13807                            start: lsp::Position {
13808                                line: 8,
13809                                character: 10,
13810                            },
13811                            end: lsp::Position {
13812                                line: 8,
13813                                character: 11,
13814                            },
13815                        },
13816                        new_text: "AGL/".to_string(),
13817                    })),
13818                    sort_text: Some("40b67681AGL/".to_string()),
13819                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
13820                    filter_text: Some("AGL/".to_string()),
13821                    insert_text: Some("AGL/".to_string()),
13822                    ..lsp::CompletionItem::default()
13823                }],
13824            })))
13825        });
13826    cx.update_editor(|editor, window, cx| {
13827        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13828    });
13829    cx.executor().run_until_parked();
13830    cx.update_editor(|editor, window, cx| {
13831        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
13832    });
13833    cx.executor().run_until_parked();
13834    cx.assert_editor_state(
13835        r##"#ifndef BAR_H
13836#define BAR_H
13837
13838#include <stdbool.h>
13839
13840int fn_branch(bool do_branch1, bool do_branch2);
13841
13842#endif // BAR_H
13843#include "AGL/ˇ"##,
13844    );
13845
13846    cx.update_editor(|editor, window, cx| {
13847        editor.handle_input("\"", window, cx);
13848    });
13849    cx.executor().run_until_parked();
13850    cx.assert_editor_state(
13851        r##"#ifndef BAR_H
13852#define BAR_H
13853
13854#include <stdbool.h>
13855
13856int fn_branch(bool do_branch1, bool do_branch2);
13857
13858#endif // BAR_H
13859#include "AGL/"ˇ"##,
13860    );
13861}
13862
13863#[gpui::test]
13864async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
13865    init_test(cx, |_| {});
13866
13867    let mut cx = EditorLspTestContext::new_rust(
13868        lsp::ServerCapabilities {
13869            completion_provider: Some(lsp::CompletionOptions {
13870                trigger_characters: Some(vec![".".to_string()]),
13871                resolve_provider: Some(true),
13872                ..Default::default()
13873            }),
13874            ..Default::default()
13875        },
13876        cx,
13877    )
13878    .await;
13879
13880    cx.set_state("fn main() { let a = 2ˇ; }");
13881    cx.simulate_keystroke(".");
13882    let completion_item = lsp::CompletionItem {
13883        label: "Some".into(),
13884        kind: Some(lsp::CompletionItemKind::SNIPPET),
13885        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
13886        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
13887            kind: lsp::MarkupKind::Markdown,
13888            value: "```rust\nSome(2)\n```".to_string(),
13889        })),
13890        deprecated: Some(false),
13891        sort_text: Some("Some".to_string()),
13892        filter_text: Some("Some".to_string()),
13893        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
13894        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13895            range: lsp::Range {
13896                start: lsp::Position {
13897                    line: 0,
13898                    character: 22,
13899                },
13900                end: lsp::Position {
13901                    line: 0,
13902                    character: 22,
13903                },
13904            },
13905            new_text: "Some(2)".to_string(),
13906        })),
13907        additional_text_edits: Some(vec![lsp::TextEdit {
13908            range: lsp::Range {
13909                start: lsp::Position {
13910                    line: 0,
13911                    character: 20,
13912                },
13913                end: lsp::Position {
13914                    line: 0,
13915                    character: 22,
13916                },
13917            },
13918            new_text: "".to_string(),
13919        }]),
13920        ..Default::default()
13921    };
13922
13923    let closure_completion_item = completion_item.clone();
13924    let counter = Arc::new(AtomicUsize::new(0));
13925    let counter_clone = counter.clone();
13926    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
13927        let task_completion_item = closure_completion_item.clone();
13928        counter_clone.fetch_add(1, atomic::Ordering::Release);
13929        async move {
13930            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
13931                is_incomplete: true,
13932                item_defaults: None,
13933                items: vec![task_completion_item],
13934            })))
13935        }
13936    });
13937
13938    cx.condition(|editor, _| editor.context_menu_visible())
13939        .await;
13940    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
13941    assert!(request.next().await.is_some());
13942    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13943
13944    cx.simulate_keystrokes("S o m");
13945    cx.condition(|editor, _| editor.context_menu_visible())
13946        .await;
13947    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
13948    assert!(request.next().await.is_some());
13949    assert!(request.next().await.is_some());
13950    assert!(request.next().await.is_some());
13951    request.close();
13952    assert!(request.next().await.is_none());
13953    assert_eq!(
13954        counter.load(atomic::Ordering::Acquire),
13955        4,
13956        "With the completions menu open, only one LSP request should happen per input"
13957    );
13958}
13959
13960#[gpui::test]
13961async fn test_toggle_comment(cx: &mut TestAppContext) {
13962    init_test(cx, |_| {});
13963    let mut cx = EditorTestContext::new(cx).await;
13964    let language = Arc::new(Language::new(
13965        LanguageConfig {
13966            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
13967            ..Default::default()
13968        },
13969        Some(tree_sitter_rust::LANGUAGE.into()),
13970    ));
13971    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
13972
13973    // If multiple selections intersect a line, the line is only toggled once.
13974    cx.set_state(indoc! {"
13975        fn a() {
13976            «//b();
13977            ˇ»// «c();
13978            //ˇ»  d();
13979        }
13980    "});
13981
13982    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13983
13984    cx.assert_editor_state(indoc! {"
13985        fn a() {
13986            «b();
13987            c();
13988            ˇ» d();
13989        }
13990    "});
13991
13992    // The comment prefix is inserted at the same column for every line in a
13993    // selection.
13994    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13995
13996    cx.assert_editor_state(indoc! {"
13997        fn a() {
13998            // «b();
13999            // c();
14000            ˇ»//  d();
14001        }
14002    "});
14003
14004    // If a selection ends at the beginning of a line, that line is not toggled.
14005    cx.set_selections_state(indoc! {"
14006        fn a() {
14007            // b();
14008            «// c();
14009        ˇ»    //  d();
14010        }
14011    "});
14012
14013    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
14014
14015    cx.assert_editor_state(indoc! {"
14016        fn a() {
14017            // b();
14018            «c();
14019        ˇ»    //  d();
14020        }
14021    "});
14022
14023    // If a selection span a single line and is empty, the line is toggled.
14024    cx.set_state(indoc! {"
14025        fn a() {
14026            a();
14027            b();
14028        ˇ
14029        }
14030    "});
14031
14032    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
14033
14034    cx.assert_editor_state(indoc! {"
14035        fn a() {
14036            a();
14037            b();
14038        //•ˇ
14039        }
14040    "});
14041
14042    // If a selection span multiple lines, empty lines are not toggled.
14043    cx.set_state(indoc! {"
14044        fn a() {
14045            «a();
14046
14047            c();ˇ»
14048        }
14049    "});
14050
14051    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
14052
14053    cx.assert_editor_state(indoc! {"
14054        fn a() {
14055            // «a();
14056
14057            // c();ˇ»
14058        }
14059    "});
14060
14061    // If a selection includes multiple comment prefixes, all lines are uncommented.
14062    cx.set_state(indoc! {"
14063        fn a() {
14064            «// a();
14065            /// b();
14066            //! c();ˇ»
14067        }
14068    "});
14069
14070    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
14071
14072    cx.assert_editor_state(indoc! {"
14073        fn a() {
14074            «a();
14075            b();
14076            c();ˇ»
14077        }
14078    "});
14079}
14080
14081#[gpui::test]
14082async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
14083    init_test(cx, |_| {});
14084    let mut cx = EditorTestContext::new(cx).await;
14085    let language = Arc::new(Language::new(
14086        LanguageConfig {
14087            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
14088            ..Default::default()
14089        },
14090        Some(tree_sitter_rust::LANGUAGE.into()),
14091    ));
14092    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
14093
14094    let toggle_comments = &ToggleComments {
14095        advance_downwards: false,
14096        ignore_indent: true,
14097    };
14098
14099    // If multiple selections intersect a line, the line is only toggled once.
14100    cx.set_state(indoc! {"
14101        fn a() {
14102        //    «b();
14103        //    c();
14104        //    ˇ» d();
14105        }
14106    "});
14107
14108    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
14109
14110    cx.assert_editor_state(indoc! {"
14111        fn a() {
14112            «b();
14113            c();
14114            ˇ» d();
14115        }
14116    "});
14117
14118    // The comment prefix is inserted at the beginning of each line
14119    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
14120
14121    cx.assert_editor_state(indoc! {"
14122        fn a() {
14123        //    «b();
14124        //    c();
14125        //    ˇ» d();
14126        }
14127    "});
14128
14129    // If a selection ends at the beginning of a line, that line is not toggled.
14130    cx.set_selections_state(indoc! {"
14131        fn a() {
14132        //    b();
14133        //    «c();
14134        ˇ»//     d();
14135        }
14136    "});
14137
14138    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
14139
14140    cx.assert_editor_state(indoc! {"
14141        fn a() {
14142        //    b();
14143            «c();
14144        ˇ»//     d();
14145        }
14146    "});
14147
14148    // If a selection span a single line and is empty, the line is toggled.
14149    cx.set_state(indoc! {"
14150        fn a() {
14151            a();
14152            b();
14153        ˇ
14154        }
14155    "});
14156
14157    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
14158
14159    cx.assert_editor_state(indoc! {"
14160        fn a() {
14161            a();
14162            b();
14163        //ˇ
14164        }
14165    "});
14166
14167    // If a selection span multiple lines, empty lines are not toggled.
14168    cx.set_state(indoc! {"
14169        fn a() {
14170            «a();
14171
14172            c();ˇ»
14173        }
14174    "});
14175
14176    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
14177
14178    cx.assert_editor_state(indoc! {"
14179        fn a() {
14180        //    «a();
14181
14182        //    c();ˇ»
14183        }
14184    "});
14185
14186    // If a selection includes multiple comment prefixes, all lines are uncommented.
14187    cx.set_state(indoc! {"
14188        fn a() {
14189        //    «a();
14190        ///    b();
14191        //!    c();ˇ»
14192        }
14193    "});
14194
14195    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
14196
14197    cx.assert_editor_state(indoc! {"
14198        fn a() {
14199            «a();
14200            b();
14201            c();ˇ»
14202        }
14203    "});
14204}
14205
14206#[gpui::test]
14207async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
14208    init_test(cx, |_| {});
14209
14210    let language = Arc::new(Language::new(
14211        LanguageConfig {
14212            line_comments: vec!["// ".into()],
14213            ..Default::default()
14214        },
14215        Some(tree_sitter_rust::LANGUAGE.into()),
14216    ));
14217
14218    let mut cx = EditorTestContext::new(cx).await;
14219
14220    cx.language_registry().add(language.clone());
14221    cx.update_buffer(|buffer, cx| {
14222        buffer.set_language(Some(language), cx);
14223    });
14224
14225    let toggle_comments = &ToggleComments {
14226        advance_downwards: true,
14227        ignore_indent: false,
14228    };
14229
14230    // Single cursor on one line -> advance
14231    // Cursor moves horizontally 3 characters as well on non-blank line
14232    cx.set_state(indoc!(
14233        "fn a() {
14234             ˇdog();
14235             cat();
14236        }"
14237    ));
14238    cx.update_editor(|editor, window, cx| {
14239        editor.toggle_comments(toggle_comments, window, cx);
14240    });
14241    cx.assert_editor_state(indoc!(
14242        "fn a() {
14243             // dog();
14244             catˇ();
14245        }"
14246    ));
14247
14248    // Single selection on one line -> don't advance
14249    cx.set_state(indoc!(
14250        "fn a() {
14251             «dog()ˇ»;
14252             cat();
14253        }"
14254    ));
14255    cx.update_editor(|editor, window, cx| {
14256        editor.toggle_comments(toggle_comments, window, cx);
14257    });
14258    cx.assert_editor_state(indoc!(
14259        "fn a() {
14260             // «dog()ˇ»;
14261             cat();
14262        }"
14263    ));
14264
14265    // Multiple cursors on one line -> advance
14266    cx.set_state(indoc!(
14267        "fn a() {
14268             ˇdˇog();
14269             cat();
14270        }"
14271    ));
14272    cx.update_editor(|editor, window, cx| {
14273        editor.toggle_comments(toggle_comments, window, cx);
14274    });
14275    cx.assert_editor_state(indoc!(
14276        "fn a() {
14277             // dog();
14278             catˇ(ˇ);
14279        }"
14280    ));
14281
14282    // Multiple cursors on one line, with selection -> don't advance
14283    cx.set_state(indoc!(
14284        "fn a() {
14285             ˇdˇog«()ˇ»;
14286             cat();
14287        }"
14288    ));
14289    cx.update_editor(|editor, window, cx| {
14290        editor.toggle_comments(toggle_comments, window, cx);
14291    });
14292    cx.assert_editor_state(indoc!(
14293        "fn a() {
14294             // ˇdˇog«()ˇ»;
14295             cat();
14296        }"
14297    ));
14298
14299    // Single cursor on one line -> advance
14300    // Cursor moves to column 0 on blank line
14301    cx.set_state(indoc!(
14302        "fn a() {
14303             ˇdog();
14304
14305             cat();
14306        }"
14307    ));
14308    cx.update_editor(|editor, window, cx| {
14309        editor.toggle_comments(toggle_comments, window, cx);
14310    });
14311    cx.assert_editor_state(indoc!(
14312        "fn a() {
14313             // dog();
14314        ˇ
14315             cat();
14316        }"
14317    ));
14318
14319    // Single cursor on one line -> advance
14320    // Cursor starts and ends at column 0
14321    cx.set_state(indoc!(
14322        "fn a() {
14323         ˇ    dog();
14324             cat();
14325        }"
14326    ));
14327    cx.update_editor(|editor, window, cx| {
14328        editor.toggle_comments(toggle_comments, window, cx);
14329    });
14330    cx.assert_editor_state(indoc!(
14331        "fn a() {
14332             // dog();
14333         ˇ    cat();
14334        }"
14335    ));
14336}
14337
14338#[gpui::test]
14339async fn test_toggle_block_comment(cx: &mut TestAppContext) {
14340    init_test(cx, |_| {});
14341
14342    let mut cx = EditorTestContext::new(cx).await;
14343
14344    let html_language = Arc::new(
14345        Language::new(
14346            LanguageConfig {
14347                name: "HTML".into(),
14348                block_comment: Some(BlockCommentConfig {
14349                    start: "<!-- ".into(),
14350                    prefix: "".into(),
14351                    end: " -->".into(),
14352                    tab_size: 0,
14353                }),
14354                ..Default::default()
14355            },
14356            Some(tree_sitter_html::LANGUAGE.into()),
14357        )
14358        .with_injection_query(
14359            r#"
14360            (script_element
14361                (raw_text) @injection.content
14362                (#set! injection.language "javascript"))
14363            "#,
14364        )
14365        .unwrap(),
14366    );
14367
14368    let javascript_language = Arc::new(Language::new(
14369        LanguageConfig {
14370            name: "JavaScript".into(),
14371            line_comments: vec!["// ".into()],
14372            ..Default::default()
14373        },
14374        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
14375    ));
14376
14377    cx.language_registry().add(html_language.clone());
14378    cx.language_registry().add(javascript_language.clone());
14379    cx.update_buffer(|buffer, cx| {
14380        buffer.set_language(Some(html_language), cx);
14381    });
14382
14383    // Toggle comments for empty selections
14384    cx.set_state(
14385        &r#"
14386            <p>A</p>ˇ
14387            <p>B</p>ˇ
14388            <p>C</p>ˇ
14389        "#
14390        .unindent(),
14391    );
14392    cx.update_editor(|editor, window, cx| {
14393        editor.toggle_comments(&ToggleComments::default(), window, cx)
14394    });
14395    cx.assert_editor_state(
14396        &r#"
14397            <!-- <p>A</p>ˇ -->
14398            <!-- <p>B</p>ˇ -->
14399            <!-- <p>C</p>ˇ -->
14400        "#
14401        .unindent(),
14402    );
14403    cx.update_editor(|editor, window, cx| {
14404        editor.toggle_comments(&ToggleComments::default(), window, cx)
14405    });
14406    cx.assert_editor_state(
14407        &r#"
14408            <p>A</p>ˇ
14409            <p>B</p>ˇ
14410            <p>C</p>ˇ
14411        "#
14412        .unindent(),
14413    );
14414
14415    // Toggle comments for mixture of empty and non-empty selections, where
14416    // multiple selections occupy a given line.
14417    cx.set_state(
14418        &r#"
14419            <p>A«</p>
14420            <p>ˇ»B</p>ˇ
14421            <p>C«</p>
14422            <p>ˇ»D</p>ˇ
14423        "#
14424        .unindent(),
14425    );
14426
14427    cx.update_editor(|editor, window, cx| {
14428        editor.toggle_comments(&ToggleComments::default(), window, cx)
14429    });
14430    cx.assert_editor_state(
14431        &r#"
14432            <!-- <p>A«</p>
14433            <p>ˇ»B</p>ˇ -->
14434            <!-- <p>C«</p>
14435            <p>ˇ»D</p>ˇ -->
14436        "#
14437        .unindent(),
14438    );
14439    cx.update_editor(|editor, window, cx| {
14440        editor.toggle_comments(&ToggleComments::default(), window, cx)
14441    });
14442    cx.assert_editor_state(
14443        &r#"
14444            <p>A«</p>
14445            <p>ˇ»B</p>ˇ
14446            <p>C«</p>
14447            <p>ˇ»D</p>ˇ
14448        "#
14449        .unindent(),
14450    );
14451
14452    // Toggle comments when different languages are active for different
14453    // selections.
14454    cx.set_state(
14455        &r#"
14456            ˇ<script>
14457                ˇvar x = new Y();
14458            ˇ</script>
14459        "#
14460        .unindent(),
14461    );
14462    cx.executor().run_until_parked();
14463    cx.update_editor(|editor, window, cx| {
14464        editor.toggle_comments(&ToggleComments::default(), window, cx)
14465    });
14466    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
14467    // Uncommenting and commenting from this position brings in even more wrong artifacts.
14468    cx.assert_editor_state(
14469        &r#"
14470            <!-- ˇ<script> -->
14471                // ˇvar x = new Y();
14472            <!-- ˇ</script> -->
14473        "#
14474        .unindent(),
14475    );
14476}
14477
14478#[gpui::test]
14479fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
14480    init_test(cx, |_| {});
14481
14482    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
14483    let multibuffer = cx.new(|cx| {
14484        let mut multibuffer = MultiBuffer::new(ReadWrite);
14485        multibuffer.push_excerpts(
14486            buffer.clone(),
14487            [
14488                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
14489                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
14490            ],
14491            cx,
14492        );
14493        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
14494        multibuffer
14495    });
14496
14497    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
14498    editor.update_in(cx, |editor, window, cx| {
14499        assert_eq!(editor.text(cx), "aaaa\nbbbb");
14500        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14501            s.select_ranges([
14502                Point::new(0, 0)..Point::new(0, 0),
14503                Point::new(1, 0)..Point::new(1, 0),
14504            ])
14505        });
14506
14507        editor.handle_input("X", window, cx);
14508        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
14509        assert_eq!(
14510            editor.selections.ranges(cx),
14511            [
14512                Point::new(0, 1)..Point::new(0, 1),
14513                Point::new(1, 1)..Point::new(1, 1),
14514            ]
14515        );
14516
14517        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
14518        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14519            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
14520        });
14521        editor.backspace(&Default::default(), window, cx);
14522        assert_eq!(editor.text(cx), "Xa\nbbb");
14523        assert_eq!(
14524            editor.selections.ranges(cx),
14525            [Point::new(1, 0)..Point::new(1, 0)]
14526        );
14527
14528        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14529            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
14530        });
14531        editor.backspace(&Default::default(), window, cx);
14532        assert_eq!(editor.text(cx), "X\nbb");
14533        assert_eq!(
14534            editor.selections.ranges(cx),
14535            [Point::new(0, 1)..Point::new(0, 1)]
14536        );
14537    });
14538}
14539
14540#[gpui::test]
14541fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
14542    init_test(cx, |_| {});
14543
14544    let markers = vec![('[', ']').into(), ('(', ')').into()];
14545    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
14546        indoc! {"
14547            [aaaa
14548            (bbbb]
14549            cccc)",
14550        },
14551        markers.clone(),
14552    );
14553    let excerpt_ranges = markers.into_iter().map(|marker| {
14554        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
14555        ExcerptRange::new(context.clone())
14556    });
14557    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
14558    let multibuffer = cx.new(|cx| {
14559        let mut multibuffer = MultiBuffer::new(ReadWrite);
14560        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
14561        multibuffer
14562    });
14563
14564    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
14565    editor.update_in(cx, |editor, window, cx| {
14566        let (expected_text, selection_ranges) = marked_text_ranges(
14567            indoc! {"
14568                aaaa
14569                bˇbbb
14570                bˇbbˇb
14571                cccc"
14572            },
14573            true,
14574        );
14575        assert_eq!(editor.text(cx), expected_text);
14576        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14577            s.select_ranges(selection_ranges)
14578        });
14579
14580        editor.handle_input("X", window, cx);
14581
14582        let (expected_text, expected_selections) = marked_text_ranges(
14583            indoc! {"
14584                aaaa
14585                bXˇbbXb
14586                bXˇbbXˇb
14587                cccc"
14588            },
14589            false,
14590        );
14591        assert_eq!(editor.text(cx), expected_text);
14592        assert_eq!(editor.selections.ranges(cx), expected_selections);
14593
14594        editor.newline(&Newline, window, cx);
14595        let (expected_text, expected_selections) = marked_text_ranges(
14596            indoc! {"
14597                aaaa
14598                bX
14599                ˇbbX
14600                b
14601                bX
14602                ˇbbX
14603                ˇb
14604                cccc"
14605            },
14606            false,
14607        );
14608        assert_eq!(editor.text(cx), expected_text);
14609        assert_eq!(editor.selections.ranges(cx), expected_selections);
14610    });
14611}
14612
14613#[gpui::test]
14614fn test_refresh_selections(cx: &mut TestAppContext) {
14615    init_test(cx, |_| {});
14616
14617    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
14618    let mut excerpt1_id = None;
14619    let multibuffer = cx.new(|cx| {
14620        let mut multibuffer = MultiBuffer::new(ReadWrite);
14621        excerpt1_id = multibuffer
14622            .push_excerpts(
14623                buffer.clone(),
14624                [
14625                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
14626                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
14627                ],
14628                cx,
14629            )
14630            .into_iter()
14631            .next();
14632        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
14633        multibuffer
14634    });
14635
14636    let editor = cx.add_window(|window, cx| {
14637        let mut editor = build_editor(multibuffer.clone(), window, cx);
14638        let snapshot = editor.snapshot(window, cx);
14639        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14640            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
14641        });
14642        editor.begin_selection(
14643            Point::new(2, 1).to_display_point(&snapshot),
14644            true,
14645            1,
14646            window,
14647            cx,
14648        );
14649        assert_eq!(
14650            editor.selections.ranges(cx),
14651            [
14652                Point::new(1, 3)..Point::new(1, 3),
14653                Point::new(2, 1)..Point::new(2, 1),
14654            ]
14655        );
14656        editor
14657    });
14658
14659    // Refreshing selections is a no-op when excerpts haven't changed.
14660    _ = editor.update(cx, |editor, window, cx| {
14661        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14662        assert_eq!(
14663            editor.selections.ranges(cx),
14664            [
14665                Point::new(1, 3)..Point::new(1, 3),
14666                Point::new(2, 1)..Point::new(2, 1),
14667            ]
14668        );
14669    });
14670
14671    multibuffer.update(cx, |multibuffer, cx| {
14672        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
14673    });
14674    _ = editor.update(cx, |editor, window, cx| {
14675        // Removing an excerpt causes the first selection to become degenerate.
14676        assert_eq!(
14677            editor.selections.ranges(cx),
14678            [
14679                Point::new(0, 0)..Point::new(0, 0),
14680                Point::new(0, 1)..Point::new(0, 1)
14681            ]
14682        );
14683
14684        // Refreshing selections will relocate the first selection to the original buffer
14685        // location.
14686        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14687        assert_eq!(
14688            editor.selections.ranges(cx),
14689            [
14690                Point::new(0, 1)..Point::new(0, 1),
14691                Point::new(0, 3)..Point::new(0, 3)
14692            ]
14693        );
14694        assert!(editor.selections.pending_anchor().is_some());
14695    });
14696}
14697
14698#[gpui::test]
14699fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
14700    init_test(cx, |_| {});
14701
14702    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
14703    let mut excerpt1_id = None;
14704    let multibuffer = cx.new(|cx| {
14705        let mut multibuffer = MultiBuffer::new(ReadWrite);
14706        excerpt1_id = multibuffer
14707            .push_excerpts(
14708                buffer.clone(),
14709                [
14710                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
14711                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
14712                ],
14713                cx,
14714            )
14715            .into_iter()
14716            .next();
14717        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
14718        multibuffer
14719    });
14720
14721    let editor = cx.add_window(|window, cx| {
14722        let mut editor = build_editor(multibuffer.clone(), window, cx);
14723        let snapshot = editor.snapshot(window, cx);
14724        editor.begin_selection(
14725            Point::new(1, 3).to_display_point(&snapshot),
14726            false,
14727            1,
14728            window,
14729            cx,
14730        );
14731        assert_eq!(
14732            editor.selections.ranges(cx),
14733            [Point::new(1, 3)..Point::new(1, 3)]
14734        );
14735        editor
14736    });
14737
14738    multibuffer.update(cx, |multibuffer, cx| {
14739        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
14740    });
14741    _ = editor.update(cx, |editor, window, cx| {
14742        assert_eq!(
14743            editor.selections.ranges(cx),
14744            [Point::new(0, 0)..Point::new(0, 0)]
14745        );
14746
14747        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
14748        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14749        assert_eq!(
14750            editor.selections.ranges(cx),
14751            [Point::new(0, 3)..Point::new(0, 3)]
14752        );
14753        assert!(editor.selections.pending_anchor().is_some());
14754    });
14755}
14756
14757#[gpui::test]
14758async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
14759    init_test(cx, |_| {});
14760
14761    let language = Arc::new(
14762        Language::new(
14763            LanguageConfig {
14764                brackets: BracketPairConfig {
14765                    pairs: vec![
14766                        BracketPair {
14767                            start: "{".to_string(),
14768                            end: "}".to_string(),
14769                            close: true,
14770                            surround: true,
14771                            newline: true,
14772                        },
14773                        BracketPair {
14774                            start: "/* ".to_string(),
14775                            end: " */".to_string(),
14776                            close: true,
14777                            surround: true,
14778                            newline: true,
14779                        },
14780                    ],
14781                    ..Default::default()
14782                },
14783                ..Default::default()
14784            },
14785            Some(tree_sitter_rust::LANGUAGE.into()),
14786        )
14787        .with_indents_query("")
14788        .unwrap(),
14789    );
14790
14791    let text = concat!(
14792        "{   }\n",     //
14793        "  x\n",       //
14794        "  /*   */\n", //
14795        "x\n",         //
14796        "{{} }\n",     //
14797    );
14798
14799    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
14800    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
14801    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
14802    editor
14803        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
14804        .await;
14805
14806    editor.update_in(cx, |editor, window, cx| {
14807        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14808            s.select_display_ranges([
14809                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
14810                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
14811                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
14812            ])
14813        });
14814        editor.newline(&Newline, window, cx);
14815
14816        assert_eq!(
14817            editor.buffer().read(cx).read(cx).text(),
14818            concat!(
14819                "{ \n",    // Suppress rustfmt
14820                "\n",      //
14821                "}\n",     //
14822                "  x\n",   //
14823                "  /* \n", //
14824                "  \n",    //
14825                "  */\n",  //
14826                "x\n",     //
14827                "{{} \n",  //
14828                "}\n",     //
14829            )
14830        );
14831    });
14832}
14833
14834#[gpui::test]
14835fn test_highlighted_ranges(cx: &mut TestAppContext) {
14836    init_test(cx, |_| {});
14837
14838    let editor = cx.add_window(|window, cx| {
14839        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
14840        build_editor(buffer.clone(), window, cx)
14841    });
14842
14843    _ = editor.update(cx, |editor, window, cx| {
14844        struct Type1;
14845        struct Type2;
14846
14847        let buffer = editor.buffer.read(cx).snapshot(cx);
14848
14849        let anchor_range =
14850            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
14851
14852        editor.highlight_background::<Type1>(
14853            &[
14854                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
14855                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
14856                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
14857                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
14858            ],
14859            |_| Hsla::red(),
14860            cx,
14861        );
14862        editor.highlight_background::<Type2>(
14863            &[
14864                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
14865                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
14866                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
14867                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
14868            ],
14869            |_| Hsla::green(),
14870            cx,
14871        );
14872
14873        let snapshot = editor.snapshot(window, cx);
14874        let mut highlighted_ranges = editor.background_highlights_in_range(
14875            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
14876            &snapshot,
14877            cx.theme(),
14878        );
14879        // Enforce a consistent ordering based on color without relying on the ordering of the
14880        // highlight's `TypeId` which is non-executor.
14881        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
14882        assert_eq!(
14883            highlighted_ranges,
14884            &[
14885                (
14886                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
14887                    Hsla::red(),
14888                ),
14889                (
14890                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
14891                    Hsla::red(),
14892                ),
14893                (
14894                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
14895                    Hsla::green(),
14896                ),
14897                (
14898                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
14899                    Hsla::green(),
14900                ),
14901            ]
14902        );
14903        assert_eq!(
14904            editor.background_highlights_in_range(
14905                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
14906                &snapshot,
14907                cx.theme(),
14908            ),
14909            &[(
14910                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
14911                Hsla::red(),
14912            )]
14913        );
14914    });
14915}
14916
14917#[gpui::test]
14918async fn test_following(cx: &mut TestAppContext) {
14919    init_test(cx, |_| {});
14920
14921    let fs = FakeFs::new(cx.executor());
14922    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
14923
14924    let buffer = project.update(cx, |project, cx| {
14925        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
14926        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
14927    });
14928    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
14929    let follower = cx.update(|cx| {
14930        cx.open_window(
14931            WindowOptions {
14932                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
14933                    gpui::Point::new(px(0.), px(0.)),
14934                    gpui::Point::new(px(10.), px(80.)),
14935                ))),
14936                ..Default::default()
14937            },
14938            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
14939        )
14940        .unwrap()
14941    });
14942
14943    let is_still_following = Rc::new(RefCell::new(true));
14944    let follower_edit_event_count = Rc::new(RefCell::new(0));
14945    let pending_update = Rc::new(RefCell::new(None));
14946    let leader_entity = leader.root(cx).unwrap();
14947    let follower_entity = follower.root(cx).unwrap();
14948    _ = follower.update(cx, {
14949        let update = pending_update.clone();
14950        let is_still_following = is_still_following.clone();
14951        let follower_edit_event_count = follower_edit_event_count.clone();
14952        |_, window, cx| {
14953            cx.subscribe_in(
14954                &leader_entity,
14955                window,
14956                move |_, leader, event, window, cx| {
14957                    leader.read(cx).add_event_to_update_proto(
14958                        event,
14959                        &mut update.borrow_mut(),
14960                        window,
14961                        cx,
14962                    );
14963                },
14964            )
14965            .detach();
14966
14967            cx.subscribe_in(
14968                &follower_entity,
14969                window,
14970                move |_, _, event: &EditorEvent, _window, _cx| {
14971                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
14972                        *is_still_following.borrow_mut() = false;
14973                    }
14974
14975                    if let EditorEvent::BufferEdited = event {
14976                        *follower_edit_event_count.borrow_mut() += 1;
14977                    }
14978                },
14979            )
14980            .detach();
14981        }
14982    });
14983
14984    // Update the selections only
14985    _ = leader.update(cx, |leader, window, cx| {
14986        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14987            s.select_ranges([1..1])
14988        });
14989    });
14990    follower
14991        .update(cx, |follower, window, cx| {
14992            follower.apply_update_proto(
14993                &project,
14994                pending_update.borrow_mut().take().unwrap(),
14995                window,
14996                cx,
14997            )
14998        })
14999        .unwrap()
15000        .await
15001        .unwrap();
15002    _ = follower.update(cx, |follower, _, cx| {
15003        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
15004    });
15005    assert!(*is_still_following.borrow());
15006    assert_eq!(*follower_edit_event_count.borrow(), 0);
15007
15008    // Update the scroll position only
15009    _ = leader.update(cx, |leader, window, cx| {
15010        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
15011    });
15012    follower
15013        .update(cx, |follower, window, cx| {
15014            follower.apply_update_proto(
15015                &project,
15016                pending_update.borrow_mut().take().unwrap(),
15017                window,
15018                cx,
15019            )
15020        })
15021        .unwrap()
15022        .await
15023        .unwrap();
15024    assert_eq!(
15025        follower
15026            .update(cx, |follower, _, cx| follower.scroll_position(cx))
15027            .unwrap(),
15028        gpui::Point::new(1.5, 3.5)
15029    );
15030    assert!(*is_still_following.borrow());
15031    assert_eq!(*follower_edit_event_count.borrow(), 0);
15032
15033    // Update the selections and scroll position. The follower's scroll position is updated
15034    // via autoscroll, not via the leader's exact scroll position.
15035    _ = leader.update(cx, |leader, window, cx| {
15036        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15037            s.select_ranges([0..0])
15038        });
15039        leader.request_autoscroll(Autoscroll::newest(), cx);
15040        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
15041    });
15042    follower
15043        .update(cx, |follower, window, cx| {
15044            follower.apply_update_proto(
15045                &project,
15046                pending_update.borrow_mut().take().unwrap(),
15047                window,
15048                cx,
15049            )
15050        })
15051        .unwrap()
15052        .await
15053        .unwrap();
15054    _ = follower.update(cx, |follower, _, cx| {
15055        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
15056        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
15057    });
15058    assert!(*is_still_following.borrow());
15059
15060    // Creating a pending selection that precedes another selection
15061    _ = leader.update(cx, |leader, window, cx| {
15062        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15063            s.select_ranges([1..1])
15064        });
15065        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
15066    });
15067    follower
15068        .update(cx, |follower, window, cx| {
15069            follower.apply_update_proto(
15070                &project,
15071                pending_update.borrow_mut().take().unwrap(),
15072                window,
15073                cx,
15074            )
15075        })
15076        .unwrap()
15077        .await
15078        .unwrap();
15079    _ = follower.update(cx, |follower, _, cx| {
15080        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
15081    });
15082    assert!(*is_still_following.borrow());
15083
15084    // Extend the pending selection so that it surrounds another selection
15085    _ = leader.update(cx, |leader, window, cx| {
15086        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
15087    });
15088    follower
15089        .update(cx, |follower, window, cx| {
15090            follower.apply_update_proto(
15091                &project,
15092                pending_update.borrow_mut().take().unwrap(),
15093                window,
15094                cx,
15095            )
15096        })
15097        .unwrap()
15098        .await
15099        .unwrap();
15100    _ = follower.update(cx, |follower, _, cx| {
15101        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
15102    });
15103
15104    // Scrolling locally breaks the follow
15105    _ = follower.update(cx, |follower, window, cx| {
15106        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
15107        follower.set_scroll_anchor(
15108            ScrollAnchor {
15109                anchor: top_anchor,
15110                offset: gpui::Point::new(0.0, 0.5),
15111            },
15112            window,
15113            cx,
15114        );
15115    });
15116    assert!(!(*is_still_following.borrow()));
15117}
15118
15119#[gpui::test]
15120async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
15121    init_test(cx, |_| {});
15122
15123    let fs = FakeFs::new(cx.executor());
15124    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
15125    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15126    let pane = workspace
15127        .update(cx, |workspace, _, _| workspace.active_pane().clone())
15128        .unwrap();
15129
15130    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
15131
15132    let leader = pane.update_in(cx, |_, window, cx| {
15133        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
15134        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
15135    });
15136
15137    // Start following the editor when it has no excerpts.
15138    let mut state_message =
15139        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
15140    let workspace_entity = workspace.root(cx).unwrap();
15141    let follower_1 = cx
15142        .update_window(*workspace.deref(), |_, window, cx| {
15143            Editor::from_state_proto(
15144                workspace_entity,
15145                ViewId {
15146                    creator: CollaboratorId::PeerId(PeerId::default()),
15147                    id: 0,
15148                },
15149                &mut state_message,
15150                window,
15151                cx,
15152            )
15153        })
15154        .unwrap()
15155        .unwrap()
15156        .await
15157        .unwrap();
15158
15159    let update_message = Rc::new(RefCell::new(None));
15160    follower_1.update_in(cx, {
15161        let update = update_message.clone();
15162        |_, window, cx| {
15163            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
15164                leader.read(cx).add_event_to_update_proto(
15165                    event,
15166                    &mut update.borrow_mut(),
15167                    window,
15168                    cx,
15169                );
15170            })
15171            .detach();
15172        }
15173    });
15174
15175    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
15176        (
15177            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
15178            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
15179        )
15180    });
15181
15182    // Insert some excerpts.
15183    leader.update(cx, |leader, cx| {
15184        leader.buffer.update(cx, |multibuffer, cx| {
15185            multibuffer.set_excerpts_for_path(
15186                PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
15187                buffer_1.clone(),
15188                vec![
15189                    Point::row_range(0..3),
15190                    Point::row_range(1..6),
15191                    Point::row_range(12..15),
15192                ],
15193                0,
15194                cx,
15195            );
15196            multibuffer.set_excerpts_for_path(
15197                PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
15198                buffer_2.clone(),
15199                vec![Point::row_range(0..6), Point::row_range(8..12)],
15200                0,
15201                cx,
15202            );
15203        });
15204    });
15205
15206    // Apply the update of adding the excerpts.
15207    follower_1
15208        .update_in(cx, |follower, window, cx| {
15209            follower.apply_update_proto(
15210                &project,
15211                update_message.borrow().clone().unwrap(),
15212                window,
15213                cx,
15214            )
15215        })
15216        .await
15217        .unwrap();
15218    assert_eq!(
15219        follower_1.update(cx, |editor, cx| editor.text(cx)),
15220        leader.update(cx, |editor, cx| editor.text(cx))
15221    );
15222    update_message.borrow_mut().take();
15223
15224    // Start following separately after it already has excerpts.
15225    let mut state_message =
15226        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
15227    let workspace_entity = workspace.root(cx).unwrap();
15228    let follower_2 = cx
15229        .update_window(*workspace.deref(), |_, window, cx| {
15230            Editor::from_state_proto(
15231                workspace_entity,
15232                ViewId {
15233                    creator: CollaboratorId::PeerId(PeerId::default()),
15234                    id: 0,
15235                },
15236                &mut state_message,
15237                window,
15238                cx,
15239            )
15240        })
15241        .unwrap()
15242        .unwrap()
15243        .await
15244        .unwrap();
15245    assert_eq!(
15246        follower_2.update(cx, |editor, cx| editor.text(cx)),
15247        leader.update(cx, |editor, cx| editor.text(cx))
15248    );
15249
15250    // Remove some excerpts.
15251    leader.update(cx, |leader, cx| {
15252        leader.buffer.update(cx, |multibuffer, cx| {
15253            let excerpt_ids = multibuffer.excerpt_ids();
15254            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
15255            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
15256        });
15257    });
15258
15259    // Apply the update of removing the excerpts.
15260    follower_1
15261        .update_in(cx, |follower, window, cx| {
15262            follower.apply_update_proto(
15263                &project,
15264                update_message.borrow().clone().unwrap(),
15265                window,
15266                cx,
15267            )
15268        })
15269        .await
15270        .unwrap();
15271    follower_2
15272        .update_in(cx, |follower, window, cx| {
15273            follower.apply_update_proto(
15274                &project,
15275                update_message.borrow().clone().unwrap(),
15276                window,
15277                cx,
15278            )
15279        })
15280        .await
15281        .unwrap();
15282    update_message.borrow_mut().take();
15283    assert_eq!(
15284        follower_1.update(cx, |editor, cx| editor.text(cx)),
15285        leader.update(cx, |editor, cx| editor.text(cx))
15286    );
15287}
15288
15289#[gpui::test]
15290async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
15291    init_test(cx, |_| {});
15292
15293    let mut cx = EditorTestContext::new(cx).await;
15294    let lsp_store =
15295        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
15296
15297    cx.set_state(indoc! {"
15298        ˇfn func(abc def: i32) -> u32 {
15299        }
15300    "});
15301
15302    cx.update(|_, cx| {
15303        lsp_store.update(cx, |lsp_store, cx| {
15304            lsp_store
15305                .update_diagnostics(
15306                    LanguageServerId(0),
15307                    lsp::PublishDiagnosticsParams {
15308                        uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
15309                        version: None,
15310                        diagnostics: vec![
15311                            lsp::Diagnostic {
15312                                range: lsp::Range::new(
15313                                    lsp::Position::new(0, 11),
15314                                    lsp::Position::new(0, 12),
15315                                ),
15316                                severity: Some(lsp::DiagnosticSeverity::ERROR),
15317                                ..Default::default()
15318                            },
15319                            lsp::Diagnostic {
15320                                range: lsp::Range::new(
15321                                    lsp::Position::new(0, 12),
15322                                    lsp::Position::new(0, 15),
15323                                ),
15324                                severity: Some(lsp::DiagnosticSeverity::ERROR),
15325                                ..Default::default()
15326                            },
15327                            lsp::Diagnostic {
15328                                range: lsp::Range::new(
15329                                    lsp::Position::new(0, 25),
15330                                    lsp::Position::new(0, 28),
15331                                ),
15332                                severity: Some(lsp::DiagnosticSeverity::ERROR),
15333                                ..Default::default()
15334                            },
15335                        ],
15336                    },
15337                    None,
15338                    DiagnosticSourceKind::Pushed,
15339                    &[],
15340                    cx,
15341                )
15342                .unwrap()
15343        });
15344    });
15345
15346    executor.run_until_parked();
15347
15348    cx.update_editor(|editor, window, cx| {
15349        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
15350    });
15351
15352    cx.assert_editor_state(indoc! {"
15353        fn func(abc def: i32) -> ˇu32 {
15354        }
15355    "});
15356
15357    cx.update_editor(|editor, window, cx| {
15358        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
15359    });
15360
15361    cx.assert_editor_state(indoc! {"
15362        fn func(abc ˇdef: i32) -> u32 {
15363        }
15364    "});
15365
15366    cx.update_editor(|editor, window, cx| {
15367        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
15368    });
15369
15370    cx.assert_editor_state(indoc! {"
15371        fn func(abcˇ def: i32) -> u32 {
15372        }
15373    "});
15374
15375    cx.update_editor(|editor, window, cx| {
15376        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
15377    });
15378
15379    cx.assert_editor_state(indoc! {"
15380        fn func(abc def: i32) -> ˇu32 {
15381        }
15382    "});
15383}
15384
15385#[gpui::test]
15386async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
15387    init_test(cx, |_| {});
15388
15389    let mut cx = EditorTestContext::new(cx).await;
15390
15391    let diff_base = r#"
15392        use some::mod;
15393
15394        const A: u32 = 42;
15395
15396        fn main() {
15397            println!("hello");
15398
15399            println!("world");
15400        }
15401        "#
15402    .unindent();
15403
15404    // Edits are modified, removed, modified, added
15405    cx.set_state(
15406        &r#"
15407        use some::modified;
15408
15409        ˇ
15410        fn main() {
15411            println!("hello there");
15412
15413            println!("around the");
15414            println!("world");
15415        }
15416        "#
15417        .unindent(),
15418    );
15419
15420    cx.set_head_text(&diff_base);
15421    executor.run_until_parked();
15422
15423    cx.update_editor(|editor, window, cx| {
15424        //Wrap around the bottom of the buffer
15425        for _ in 0..3 {
15426            editor.go_to_next_hunk(&GoToHunk, window, cx);
15427        }
15428    });
15429
15430    cx.assert_editor_state(
15431        &r#"
15432        ˇuse some::modified;
15433
15434
15435        fn main() {
15436            println!("hello there");
15437
15438            println!("around the");
15439            println!("world");
15440        }
15441        "#
15442        .unindent(),
15443    );
15444
15445    cx.update_editor(|editor, window, cx| {
15446        //Wrap around the top of the buffer
15447        for _ in 0..2 {
15448            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
15449        }
15450    });
15451
15452    cx.assert_editor_state(
15453        &r#"
15454        use some::modified;
15455
15456
15457        fn main() {
15458        ˇ    println!("hello there");
15459
15460            println!("around the");
15461            println!("world");
15462        }
15463        "#
15464        .unindent(),
15465    );
15466
15467    cx.update_editor(|editor, window, cx| {
15468        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
15469    });
15470
15471    cx.assert_editor_state(
15472        &r#"
15473        use some::modified;
15474
15475        ˇ
15476        fn main() {
15477            println!("hello there");
15478
15479            println!("around the");
15480            println!("world");
15481        }
15482        "#
15483        .unindent(),
15484    );
15485
15486    cx.update_editor(|editor, window, cx| {
15487        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
15488    });
15489
15490    cx.assert_editor_state(
15491        &r#"
15492        ˇuse some::modified;
15493
15494
15495        fn main() {
15496            println!("hello there");
15497
15498            println!("around the");
15499            println!("world");
15500        }
15501        "#
15502        .unindent(),
15503    );
15504
15505    cx.update_editor(|editor, window, cx| {
15506        for _ in 0..2 {
15507            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
15508        }
15509    });
15510
15511    cx.assert_editor_state(
15512        &r#"
15513        use some::modified;
15514
15515
15516        fn main() {
15517        ˇ    println!("hello there");
15518
15519            println!("around the");
15520            println!("world");
15521        }
15522        "#
15523        .unindent(),
15524    );
15525
15526    cx.update_editor(|editor, window, cx| {
15527        editor.fold(&Fold, window, cx);
15528    });
15529
15530    cx.update_editor(|editor, window, cx| {
15531        editor.go_to_next_hunk(&GoToHunk, window, cx);
15532    });
15533
15534    cx.assert_editor_state(
15535        &r#"
15536        ˇuse some::modified;
15537
15538
15539        fn main() {
15540            println!("hello there");
15541
15542            println!("around the");
15543            println!("world");
15544        }
15545        "#
15546        .unindent(),
15547    );
15548}
15549
15550#[test]
15551fn test_split_words() {
15552    fn split(text: &str) -> Vec<&str> {
15553        split_words(text).collect()
15554    }
15555
15556    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
15557    assert_eq!(split("hello_world"), &["hello_", "world"]);
15558    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
15559    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
15560    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
15561    assert_eq!(split("helloworld"), &["helloworld"]);
15562
15563    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
15564}
15565
15566#[gpui::test]
15567async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
15568    init_test(cx, |_| {});
15569
15570    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
15571    let mut assert = |before, after| {
15572        let _state_context = cx.set_state(before);
15573        cx.run_until_parked();
15574        cx.update_editor(|editor, window, cx| {
15575            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
15576        });
15577        cx.run_until_parked();
15578        cx.assert_editor_state(after);
15579    };
15580
15581    // Outside bracket jumps to outside of matching bracket
15582    assert("console.logˇ(var);", "console.log(var)ˇ;");
15583    assert("console.log(var)ˇ;", "console.logˇ(var);");
15584
15585    // Inside bracket jumps to inside of matching bracket
15586    assert("console.log(ˇvar);", "console.log(varˇ);");
15587    assert("console.log(varˇ);", "console.log(ˇvar);");
15588
15589    // When outside a bracket and inside, favor jumping to the inside bracket
15590    assert(
15591        "console.log('foo', [1, 2, 3]ˇ);",
15592        "console.log(ˇ'foo', [1, 2, 3]);",
15593    );
15594    assert(
15595        "console.log(ˇ'foo', [1, 2, 3]);",
15596        "console.log('foo', [1, 2, 3]ˇ);",
15597    );
15598
15599    // Bias forward if two options are equally likely
15600    assert(
15601        "let result = curried_fun()ˇ();",
15602        "let result = curried_fun()()ˇ;",
15603    );
15604
15605    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
15606    assert(
15607        indoc! {"
15608            function test() {
15609                console.log('test')ˇ
15610            }"},
15611        indoc! {"
15612            function test() {
15613                console.logˇ('test')
15614            }"},
15615    );
15616}
15617
15618#[gpui::test]
15619async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
15620    init_test(cx, |_| {});
15621
15622    let fs = FakeFs::new(cx.executor());
15623    fs.insert_tree(
15624        path!("/a"),
15625        json!({
15626            "main.rs": "fn main() { let a = 5; }",
15627            "other.rs": "// Test file",
15628        }),
15629    )
15630    .await;
15631    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15632
15633    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15634    language_registry.add(Arc::new(Language::new(
15635        LanguageConfig {
15636            name: "Rust".into(),
15637            matcher: LanguageMatcher {
15638                path_suffixes: vec!["rs".to_string()],
15639                ..Default::default()
15640            },
15641            brackets: BracketPairConfig {
15642                pairs: vec![BracketPair {
15643                    start: "{".to_string(),
15644                    end: "}".to_string(),
15645                    close: true,
15646                    surround: true,
15647                    newline: true,
15648                }],
15649                disabled_scopes_by_bracket_ix: Vec::new(),
15650            },
15651            ..Default::default()
15652        },
15653        Some(tree_sitter_rust::LANGUAGE.into()),
15654    )));
15655    let mut fake_servers = language_registry.register_fake_lsp(
15656        "Rust",
15657        FakeLspAdapter {
15658            capabilities: lsp::ServerCapabilities {
15659                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
15660                    first_trigger_character: "{".to_string(),
15661                    more_trigger_character: None,
15662                }),
15663                ..Default::default()
15664            },
15665            ..Default::default()
15666        },
15667    );
15668
15669    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15670
15671    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15672
15673    let worktree_id = workspace
15674        .update(cx, |workspace, _, cx| {
15675            workspace.project().update(cx, |project, cx| {
15676                project.worktrees(cx).next().unwrap().read(cx).id()
15677            })
15678        })
15679        .unwrap();
15680
15681    let buffer = project
15682        .update(cx, |project, cx| {
15683            project.open_local_buffer(path!("/a/main.rs"), cx)
15684        })
15685        .await
15686        .unwrap();
15687    let editor_handle = workspace
15688        .update(cx, |workspace, window, cx| {
15689            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
15690        })
15691        .unwrap()
15692        .await
15693        .unwrap()
15694        .downcast::<Editor>()
15695        .unwrap();
15696
15697    cx.executor().start_waiting();
15698    let fake_server = fake_servers.next().await.unwrap();
15699
15700    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
15701        |params, _| async move {
15702            assert_eq!(
15703                params.text_document_position.text_document.uri,
15704                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
15705            );
15706            assert_eq!(
15707                params.text_document_position.position,
15708                lsp::Position::new(0, 21),
15709            );
15710
15711            Ok(Some(vec![lsp::TextEdit {
15712                new_text: "]".to_string(),
15713                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15714            }]))
15715        },
15716    );
15717
15718    editor_handle.update_in(cx, |editor, window, cx| {
15719        window.focus(&editor.focus_handle(cx));
15720        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15721            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
15722        });
15723        editor.handle_input("{", window, cx);
15724    });
15725
15726    cx.executor().run_until_parked();
15727
15728    buffer.update(cx, |buffer, _| {
15729        assert_eq!(
15730            buffer.text(),
15731            "fn main() { let a = {5}; }",
15732            "No extra braces from on type formatting should appear in the buffer"
15733        )
15734    });
15735}
15736
15737#[gpui::test(iterations = 20, seeds(31))]
15738async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
15739    init_test(cx, |_| {});
15740
15741    let mut cx = EditorLspTestContext::new_rust(
15742        lsp::ServerCapabilities {
15743            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
15744                first_trigger_character: ".".to_string(),
15745                more_trigger_character: None,
15746            }),
15747            ..Default::default()
15748        },
15749        cx,
15750    )
15751    .await;
15752
15753    cx.update_buffer(|buffer, _| {
15754        // This causes autoindent to be async.
15755        buffer.set_sync_parse_timeout(Duration::ZERO)
15756    });
15757
15758    cx.set_state("fn c() {\n    d()ˇ\n}\n");
15759    cx.simulate_keystroke("\n");
15760    cx.run_until_parked();
15761
15762    let buffer_cloned =
15763        cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap().clone());
15764    let mut request =
15765        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
15766            let buffer_cloned = buffer_cloned.clone();
15767            async move {
15768                buffer_cloned.update(&mut cx, |buffer, _| {
15769                    assert_eq!(
15770                        buffer.text(),
15771                        "fn c() {\n    d()\n        .\n}\n",
15772                        "OnTypeFormatting should triggered after autoindent applied"
15773                    )
15774                })?;
15775
15776                Ok(Some(vec![]))
15777            }
15778        });
15779
15780    cx.simulate_keystroke(".");
15781    cx.run_until_parked();
15782
15783    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
15784    assert!(request.next().await.is_some());
15785    request.close();
15786    assert!(request.next().await.is_none());
15787}
15788
15789#[gpui::test]
15790async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
15791    init_test(cx, |_| {});
15792
15793    let fs = FakeFs::new(cx.executor());
15794    fs.insert_tree(
15795        path!("/a"),
15796        json!({
15797            "main.rs": "fn main() { let a = 5; }",
15798            "other.rs": "// Test file",
15799        }),
15800    )
15801    .await;
15802
15803    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15804
15805    let server_restarts = Arc::new(AtomicUsize::new(0));
15806    let closure_restarts = Arc::clone(&server_restarts);
15807    let language_server_name = "test language server";
15808    let language_name: LanguageName = "Rust".into();
15809
15810    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15811    language_registry.add(Arc::new(Language::new(
15812        LanguageConfig {
15813            name: language_name.clone(),
15814            matcher: LanguageMatcher {
15815                path_suffixes: vec!["rs".to_string()],
15816                ..Default::default()
15817            },
15818            ..Default::default()
15819        },
15820        Some(tree_sitter_rust::LANGUAGE.into()),
15821    )));
15822    let mut fake_servers = language_registry.register_fake_lsp(
15823        "Rust",
15824        FakeLspAdapter {
15825            name: language_server_name,
15826            initialization_options: Some(json!({
15827                "testOptionValue": true
15828            })),
15829            initializer: Some(Box::new(move |fake_server| {
15830                let task_restarts = Arc::clone(&closure_restarts);
15831                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
15832                    task_restarts.fetch_add(1, atomic::Ordering::Release);
15833                    futures::future::ready(Ok(()))
15834                });
15835            })),
15836            ..Default::default()
15837        },
15838    );
15839
15840    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15841    let _buffer = project
15842        .update(cx, |project, cx| {
15843            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
15844        })
15845        .await
15846        .unwrap();
15847    let _fake_server = fake_servers.next().await.unwrap();
15848    update_test_language_settings(cx, |language_settings| {
15849        language_settings.languages.0.insert(
15850            language_name.clone(),
15851            LanguageSettingsContent {
15852                tab_size: NonZeroU32::new(8),
15853                ..Default::default()
15854            },
15855        );
15856    });
15857    cx.executor().run_until_parked();
15858    assert_eq!(
15859        server_restarts.load(atomic::Ordering::Acquire),
15860        0,
15861        "Should not restart LSP server on an unrelated change"
15862    );
15863
15864    update_test_project_settings(cx, |project_settings| {
15865        project_settings.lsp.insert(
15866            "Some other server name".into(),
15867            LspSettings {
15868                binary: None,
15869                settings: None,
15870                initialization_options: Some(json!({
15871                    "some other init value": false
15872                })),
15873                enable_lsp_tasks: false,
15874            },
15875        );
15876    });
15877    cx.executor().run_until_parked();
15878    assert_eq!(
15879        server_restarts.load(atomic::Ordering::Acquire),
15880        0,
15881        "Should not restart LSP server on an unrelated LSP settings change"
15882    );
15883
15884    update_test_project_settings(cx, |project_settings| {
15885        project_settings.lsp.insert(
15886            language_server_name.into(),
15887            LspSettings {
15888                binary: None,
15889                settings: None,
15890                initialization_options: Some(json!({
15891                    "anotherInitValue": false
15892                })),
15893                enable_lsp_tasks: false,
15894            },
15895        );
15896    });
15897    cx.executor().run_until_parked();
15898    assert_eq!(
15899        server_restarts.load(atomic::Ordering::Acquire),
15900        1,
15901        "Should restart LSP server on a related LSP settings change"
15902    );
15903
15904    update_test_project_settings(cx, |project_settings| {
15905        project_settings.lsp.insert(
15906            language_server_name.into(),
15907            LspSettings {
15908                binary: None,
15909                settings: None,
15910                initialization_options: Some(json!({
15911                    "anotherInitValue": false
15912                })),
15913                enable_lsp_tasks: false,
15914            },
15915        );
15916    });
15917    cx.executor().run_until_parked();
15918    assert_eq!(
15919        server_restarts.load(atomic::Ordering::Acquire),
15920        1,
15921        "Should not restart LSP server on a related LSP settings change that is the same"
15922    );
15923
15924    update_test_project_settings(cx, |project_settings| {
15925        project_settings.lsp.insert(
15926            language_server_name.into(),
15927            LspSettings {
15928                binary: None,
15929                settings: None,
15930                initialization_options: None,
15931                enable_lsp_tasks: false,
15932            },
15933        );
15934    });
15935    cx.executor().run_until_parked();
15936    assert_eq!(
15937        server_restarts.load(atomic::Ordering::Acquire),
15938        2,
15939        "Should restart LSP server on another related LSP settings change"
15940    );
15941}
15942
15943#[gpui::test]
15944async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
15945    init_test(cx, |_| {});
15946
15947    let mut cx = EditorLspTestContext::new_rust(
15948        lsp::ServerCapabilities {
15949            completion_provider: Some(lsp::CompletionOptions {
15950                trigger_characters: Some(vec![".".to_string()]),
15951                resolve_provider: Some(true),
15952                ..Default::default()
15953            }),
15954            ..Default::default()
15955        },
15956        cx,
15957    )
15958    .await;
15959
15960    cx.set_state("fn main() { let a = 2ˇ; }");
15961    cx.simulate_keystroke(".");
15962    let completion_item = lsp::CompletionItem {
15963        label: "some".into(),
15964        kind: Some(lsp::CompletionItemKind::SNIPPET),
15965        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15966        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15967            kind: lsp::MarkupKind::Markdown,
15968            value: "```rust\nSome(2)\n```".to_string(),
15969        })),
15970        deprecated: Some(false),
15971        sort_text: Some("fffffff2".to_string()),
15972        filter_text: Some("some".to_string()),
15973        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15974        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15975            range: lsp::Range {
15976                start: lsp::Position {
15977                    line: 0,
15978                    character: 22,
15979                },
15980                end: lsp::Position {
15981                    line: 0,
15982                    character: 22,
15983                },
15984            },
15985            new_text: "Some(2)".to_string(),
15986        })),
15987        additional_text_edits: Some(vec![lsp::TextEdit {
15988            range: lsp::Range {
15989                start: lsp::Position {
15990                    line: 0,
15991                    character: 20,
15992                },
15993                end: lsp::Position {
15994                    line: 0,
15995                    character: 22,
15996                },
15997            },
15998            new_text: "".to_string(),
15999        }]),
16000        ..Default::default()
16001    };
16002
16003    let closure_completion_item = completion_item.clone();
16004    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16005        let task_completion_item = closure_completion_item.clone();
16006        async move {
16007            Ok(Some(lsp::CompletionResponse::Array(vec![
16008                task_completion_item,
16009            ])))
16010        }
16011    });
16012
16013    request.next().await;
16014
16015    cx.condition(|editor, _| editor.context_menu_visible())
16016        .await;
16017    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
16018        editor
16019            .confirm_completion(&ConfirmCompletion::default(), window, cx)
16020            .unwrap()
16021    });
16022    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
16023
16024    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
16025        let task_completion_item = completion_item.clone();
16026        async move { Ok(task_completion_item) }
16027    })
16028    .next()
16029    .await
16030    .unwrap();
16031    apply_additional_edits.await.unwrap();
16032    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
16033}
16034
16035#[gpui::test]
16036async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
16037    init_test(cx, |_| {});
16038
16039    let mut cx = EditorLspTestContext::new_rust(
16040        lsp::ServerCapabilities {
16041            completion_provider: Some(lsp::CompletionOptions {
16042                trigger_characters: Some(vec![".".to_string()]),
16043                resolve_provider: Some(true),
16044                ..Default::default()
16045            }),
16046            ..Default::default()
16047        },
16048        cx,
16049    )
16050    .await;
16051
16052    cx.set_state("fn main() { let a = 2ˇ; }");
16053    cx.simulate_keystroke(".");
16054
16055    let item1 = lsp::CompletionItem {
16056        label: "method id()".to_string(),
16057        filter_text: Some("id".to_string()),
16058        detail: None,
16059        documentation: None,
16060        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16061            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16062            new_text: ".id".to_string(),
16063        })),
16064        ..lsp::CompletionItem::default()
16065    };
16066
16067    let item2 = lsp::CompletionItem {
16068        label: "other".to_string(),
16069        filter_text: Some("other".to_string()),
16070        detail: None,
16071        documentation: None,
16072        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16073            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16074            new_text: ".other".to_string(),
16075        })),
16076        ..lsp::CompletionItem::default()
16077    };
16078
16079    let item1 = item1.clone();
16080    cx.set_request_handler::<lsp::request::Completion, _, _>({
16081        let item1 = item1.clone();
16082        move |_, _, _| {
16083            let item1 = item1.clone();
16084            let item2 = item2.clone();
16085            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
16086        }
16087    })
16088    .next()
16089    .await;
16090
16091    cx.condition(|editor, _| editor.context_menu_visible())
16092        .await;
16093    cx.update_editor(|editor, _, _| {
16094        let context_menu = editor.context_menu.borrow_mut();
16095        let context_menu = context_menu
16096            .as_ref()
16097            .expect("Should have the context menu deployed");
16098        match context_menu {
16099            CodeContextMenu::Completions(completions_menu) => {
16100                let completions = completions_menu.completions.borrow_mut();
16101                assert_eq!(
16102                    completions
16103                        .iter()
16104                        .map(|completion| &completion.label.text)
16105                        .collect::<Vec<_>>(),
16106                    vec!["method id()", "other"]
16107                )
16108            }
16109            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
16110        }
16111    });
16112
16113    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
16114        let item1 = item1.clone();
16115        move |_, item_to_resolve, _| {
16116            let item1 = item1.clone();
16117            async move {
16118                if item1 == item_to_resolve {
16119                    Ok(lsp::CompletionItem {
16120                        label: "method id()".to_string(),
16121                        filter_text: Some("id".to_string()),
16122                        detail: Some("Now resolved!".to_string()),
16123                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
16124                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16125                            range: lsp::Range::new(
16126                                lsp::Position::new(0, 22),
16127                                lsp::Position::new(0, 22),
16128                            ),
16129                            new_text: ".id".to_string(),
16130                        })),
16131                        ..lsp::CompletionItem::default()
16132                    })
16133                } else {
16134                    Ok(item_to_resolve)
16135                }
16136            }
16137        }
16138    })
16139    .next()
16140    .await
16141    .unwrap();
16142    cx.run_until_parked();
16143
16144    cx.update_editor(|editor, window, cx| {
16145        editor.context_menu_next(&Default::default(), window, cx);
16146    });
16147
16148    cx.update_editor(|editor, _, _| {
16149        let context_menu = editor.context_menu.borrow_mut();
16150        let context_menu = context_menu
16151            .as_ref()
16152            .expect("Should have the context menu deployed");
16153        match context_menu {
16154            CodeContextMenu::Completions(completions_menu) => {
16155                let completions = completions_menu.completions.borrow_mut();
16156                assert_eq!(
16157                    completions
16158                        .iter()
16159                        .map(|completion| &completion.label.text)
16160                        .collect::<Vec<_>>(),
16161                    vec!["method id() Now resolved!", "other"],
16162                    "Should update first completion label, but not second as the filter text did not match."
16163                );
16164            }
16165            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
16166        }
16167    });
16168}
16169
16170#[gpui::test]
16171async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
16172    init_test(cx, |_| {});
16173    let mut cx = EditorLspTestContext::new_rust(
16174        lsp::ServerCapabilities {
16175            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
16176            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
16177            completion_provider: Some(lsp::CompletionOptions {
16178                resolve_provider: Some(true),
16179                ..Default::default()
16180            }),
16181            ..Default::default()
16182        },
16183        cx,
16184    )
16185    .await;
16186    cx.set_state(indoc! {"
16187        struct TestStruct {
16188            field: i32
16189        }
16190
16191        fn mainˇ() {
16192            let unused_var = 42;
16193            let test_struct = TestStruct { field: 42 };
16194        }
16195    "});
16196    let symbol_range = cx.lsp_range(indoc! {"
16197        struct TestStruct {
16198            field: i32
16199        }
16200
16201        «fn main»() {
16202            let unused_var = 42;
16203            let test_struct = TestStruct { field: 42 };
16204        }
16205    "});
16206    let mut hover_requests =
16207        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
16208            Ok(Some(lsp::Hover {
16209                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
16210                    kind: lsp::MarkupKind::Markdown,
16211                    value: "Function documentation".to_string(),
16212                }),
16213                range: Some(symbol_range),
16214            }))
16215        });
16216
16217    // Case 1: Test that code action menu hide hover popover
16218    cx.dispatch_action(Hover);
16219    hover_requests.next().await;
16220    cx.condition(|editor, _| editor.hover_state.visible()).await;
16221    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
16222        move |_, _, _| async move {
16223            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
16224                lsp::CodeAction {
16225                    title: "Remove unused variable".to_string(),
16226                    kind: Some(CodeActionKind::QUICKFIX),
16227                    edit: Some(lsp::WorkspaceEdit {
16228                        changes: Some(
16229                            [(
16230                                lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
16231                                vec![lsp::TextEdit {
16232                                    range: lsp::Range::new(
16233                                        lsp::Position::new(5, 4),
16234                                        lsp::Position::new(5, 27),
16235                                    ),
16236                                    new_text: "".to_string(),
16237                                }],
16238                            )]
16239                            .into_iter()
16240                            .collect(),
16241                        ),
16242                        ..Default::default()
16243                    }),
16244                    ..Default::default()
16245                },
16246            )]))
16247        },
16248    );
16249    cx.update_editor(|editor, window, cx| {
16250        editor.toggle_code_actions(
16251            &ToggleCodeActions {
16252                deployed_from: None,
16253                quick_launch: false,
16254            },
16255            window,
16256            cx,
16257        );
16258    });
16259    code_action_requests.next().await;
16260    cx.run_until_parked();
16261    cx.condition(|editor, _| editor.context_menu_visible())
16262        .await;
16263    cx.update_editor(|editor, _, _| {
16264        assert!(
16265            !editor.hover_state.visible(),
16266            "Hover popover should be hidden when code action menu is shown"
16267        );
16268        // Hide code actions
16269        editor.context_menu.take();
16270    });
16271
16272    // Case 2: Test that code completions hide hover popover
16273    cx.dispatch_action(Hover);
16274    hover_requests.next().await;
16275    cx.condition(|editor, _| editor.hover_state.visible()).await;
16276    let counter = Arc::new(AtomicUsize::new(0));
16277    let mut completion_requests =
16278        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16279            let counter = counter.clone();
16280            async move {
16281                counter.fetch_add(1, atomic::Ordering::Release);
16282                Ok(Some(lsp::CompletionResponse::Array(vec![
16283                    lsp::CompletionItem {
16284                        label: "main".into(),
16285                        kind: Some(lsp::CompletionItemKind::FUNCTION),
16286                        detail: Some("() -> ()".to_string()),
16287                        ..Default::default()
16288                    },
16289                    lsp::CompletionItem {
16290                        label: "TestStruct".into(),
16291                        kind: Some(lsp::CompletionItemKind::STRUCT),
16292                        detail: Some("struct TestStruct".to_string()),
16293                        ..Default::default()
16294                    },
16295                ])))
16296            }
16297        });
16298    cx.update_editor(|editor, window, cx| {
16299        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
16300    });
16301    completion_requests.next().await;
16302    cx.condition(|editor, _| editor.context_menu_visible())
16303        .await;
16304    cx.update_editor(|editor, _, _| {
16305        assert!(
16306            !editor.hover_state.visible(),
16307            "Hover popover should be hidden when completion menu is shown"
16308        );
16309    });
16310}
16311
16312#[gpui::test]
16313async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
16314    init_test(cx, |_| {});
16315
16316    let mut cx = EditorLspTestContext::new_rust(
16317        lsp::ServerCapabilities {
16318            completion_provider: Some(lsp::CompletionOptions {
16319                trigger_characters: Some(vec![".".to_string()]),
16320                resolve_provider: Some(true),
16321                ..Default::default()
16322            }),
16323            ..Default::default()
16324        },
16325        cx,
16326    )
16327    .await;
16328
16329    cx.set_state("fn main() { let a = 2ˇ; }");
16330    cx.simulate_keystroke(".");
16331
16332    let unresolved_item_1 = lsp::CompletionItem {
16333        label: "id".to_string(),
16334        filter_text: Some("id".to_string()),
16335        detail: None,
16336        documentation: None,
16337        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16338            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16339            new_text: ".id".to_string(),
16340        })),
16341        ..lsp::CompletionItem::default()
16342    };
16343    let resolved_item_1 = lsp::CompletionItem {
16344        additional_text_edits: Some(vec![lsp::TextEdit {
16345            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
16346            new_text: "!!".to_string(),
16347        }]),
16348        ..unresolved_item_1.clone()
16349    };
16350    let unresolved_item_2 = lsp::CompletionItem {
16351        label: "other".to_string(),
16352        filter_text: Some("other".to_string()),
16353        detail: None,
16354        documentation: None,
16355        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16356            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16357            new_text: ".other".to_string(),
16358        })),
16359        ..lsp::CompletionItem::default()
16360    };
16361    let resolved_item_2 = lsp::CompletionItem {
16362        additional_text_edits: Some(vec![lsp::TextEdit {
16363            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
16364            new_text: "??".to_string(),
16365        }]),
16366        ..unresolved_item_2.clone()
16367    };
16368
16369    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
16370    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
16371    cx.lsp
16372        .server
16373        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
16374            let unresolved_item_1 = unresolved_item_1.clone();
16375            let resolved_item_1 = resolved_item_1.clone();
16376            let unresolved_item_2 = unresolved_item_2.clone();
16377            let resolved_item_2 = resolved_item_2.clone();
16378            let resolve_requests_1 = resolve_requests_1.clone();
16379            let resolve_requests_2 = resolve_requests_2.clone();
16380            move |unresolved_request, _| {
16381                let unresolved_item_1 = unresolved_item_1.clone();
16382                let resolved_item_1 = resolved_item_1.clone();
16383                let unresolved_item_2 = unresolved_item_2.clone();
16384                let resolved_item_2 = resolved_item_2.clone();
16385                let resolve_requests_1 = resolve_requests_1.clone();
16386                let resolve_requests_2 = resolve_requests_2.clone();
16387                async move {
16388                    if unresolved_request == unresolved_item_1 {
16389                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
16390                        Ok(resolved_item_1.clone())
16391                    } else if unresolved_request == unresolved_item_2 {
16392                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
16393                        Ok(resolved_item_2.clone())
16394                    } else {
16395                        panic!("Unexpected completion item {unresolved_request:?}")
16396                    }
16397                }
16398            }
16399        })
16400        .detach();
16401
16402    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16403        let unresolved_item_1 = unresolved_item_1.clone();
16404        let unresolved_item_2 = unresolved_item_2.clone();
16405        async move {
16406            Ok(Some(lsp::CompletionResponse::Array(vec![
16407                unresolved_item_1,
16408                unresolved_item_2,
16409            ])))
16410        }
16411    })
16412    .next()
16413    .await;
16414
16415    cx.condition(|editor, _| editor.context_menu_visible())
16416        .await;
16417    cx.update_editor(|editor, _, _| {
16418        let context_menu = editor.context_menu.borrow_mut();
16419        let context_menu = context_menu
16420            .as_ref()
16421            .expect("Should have the context menu deployed");
16422        match context_menu {
16423            CodeContextMenu::Completions(completions_menu) => {
16424                let completions = completions_menu.completions.borrow_mut();
16425                assert_eq!(
16426                    completions
16427                        .iter()
16428                        .map(|completion| &completion.label.text)
16429                        .collect::<Vec<_>>(),
16430                    vec!["id", "other"]
16431                )
16432            }
16433            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
16434        }
16435    });
16436    cx.run_until_parked();
16437
16438    cx.update_editor(|editor, window, cx| {
16439        editor.context_menu_next(&ContextMenuNext, window, cx);
16440    });
16441    cx.run_until_parked();
16442    cx.update_editor(|editor, window, cx| {
16443        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
16444    });
16445    cx.run_until_parked();
16446    cx.update_editor(|editor, window, cx| {
16447        editor.context_menu_next(&ContextMenuNext, window, cx);
16448    });
16449    cx.run_until_parked();
16450    cx.update_editor(|editor, window, cx| {
16451        editor
16452            .compose_completion(&ComposeCompletion::default(), window, cx)
16453            .expect("No task returned")
16454    })
16455    .await
16456    .expect("Completion failed");
16457    cx.run_until_parked();
16458
16459    cx.update_editor(|editor, _, cx| {
16460        assert_eq!(
16461            resolve_requests_1.load(atomic::Ordering::Acquire),
16462            1,
16463            "Should always resolve once despite multiple selections"
16464        );
16465        assert_eq!(
16466            resolve_requests_2.load(atomic::Ordering::Acquire),
16467            1,
16468            "Should always resolve once after multiple selections and applying the completion"
16469        );
16470        assert_eq!(
16471            editor.text(cx),
16472            "fn main() { let a = ??.other; }",
16473            "Should use resolved data when applying the completion"
16474        );
16475    });
16476}
16477
16478#[gpui::test]
16479async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
16480    init_test(cx, |_| {});
16481
16482    let item_0 = lsp::CompletionItem {
16483        label: "abs".into(),
16484        insert_text: Some("abs".into()),
16485        data: Some(json!({ "very": "special"})),
16486        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
16487        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
16488            lsp::InsertReplaceEdit {
16489                new_text: "abs".to_string(),
16490                insert: lsp::Range::default(),
16491                replace: lsp::Range::default(),
16492            },
16493        )),
16494        ..lsp::CompletionItem::default()
16495    };
16496    let items = iter::once(item_0.clone())
16497        .chain((11..51).map(|i| lsp::CompletionItem {
16498            label: format!("item_{}", i),
16499            insert_text: Some(format!("item_{}", i)),
16500            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
16501            ..lsp::CompletionItem::default()
16502        }))
16503        .collect::<Vec<_>>();
16504
16505    let default_commit_characters = vec!["?".to_string()];
16506    let default_data = json!({ "default": "data"});
16507    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
16508    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
16509    let default_edit_range = lsp::Range {
16510        start: lsp::Position {
16511            line: 0,
16512            character: 5,
16513        },
16514        end: lsp::Position {
16515            line: 0,
16516            character: 5,
16517        },
16518    };
16519
16520    let mut cx = EditorLspTestContext::new_rust(
16521        lsp::ServerCapabilities {
16522            completion_provider: Some(lsp::CompletionOptions {
16523                trigger_characters: Some(vec![".".to_string()]),
16524                resolve_provider: Some(true),
16525                ..Default::default()
16526            }),
16527            ..Default::default()
16528        },
16529        cx,
16530    )
16531    .await;
16532
16533    cx.set_state("fn main() { let a = 2ˇ; }");
16534    cx.simulate_keystroke(".");
16535
16536    let completion_data = default_data.clone();
16537    let completion_characters = default_commit_characters.clone();
16538    let completion_items = items.clone();
16539    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16540        let default_data = completion_data.clone();
16541        let default_commit_characters = completion_characters.clone();
16542        let items = completion_items.clone();
16543        async move {
16544            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16545                items,
16546                item_defaults: Some(lsp::CompletionListItemDefaults {
16547                    data: Some(default_data.clone()),
16548                    commit_characters: Some(default_commit_characters.clone()),
16549                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
16550                        default_edit_range,
16551                    )),
16552                    insert_text_format: Some(default_insert_text_format),
16553                    insert_text_mode: Some(default_insert_text_mode),
16554                }),
16555                ..lsp::CompletionList::default()
16556            })))
16557        }
16558    })
16559    .next()
16560    .await;
16561
16562    let resolved_items = Arc::new(Mutex::new(Vec::new()));
16563    cx.lsp
16564        .server
16565        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
16566            let closure_resolved_items = resolved_items.clone();
16567            move |item_to_resolve, _| {
16568                let closure_resolved_items = closure_resolved_items.clone();
16569                async move {
16570                    closure_resolved_items.lock().push(item_to_resolve.clone());
16571                    Ok(item_to_resolve)
16572                }
16573            }
16574        })
16575        .detach();
16576
16577    cx.condition(|editor, _| editor.context_menu_visible())
16578        .await;
16579    cx.run_until_parked();
16580    cx.update_editor(|editor, _, _| {
16581        let menu = editor.context_menu.borrow_mut();
16582        match menu.as_ref().expect("should have the completions menu") {
16583            CodeContextMenu::Completions(completions_menu) => {
16584                assert_eq!(
16585                    completions_menu
16586                        .entries
16587                        .borrow()
16588                        .iter()
16589                        .map(|mat| mat.string.clone())
16590                        .collect::<Vec<String>>(),
16591                    items
16592                        .iter()
16593                        .map(|completion| completion.label.clone())
16594                        .collect::<Vec<String>>()
16595                );
16596            }
16597            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
16598        }
16599    });
16600    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
16601    // with 4 from the end.
16602    assert_eq!(
16603        *resolved_items.lock(),
16604        [&items[0..16], &items[items.len() - 4..items.len()]]
16605            .concat()
16606            .iter()
16607            .cloned()
16608            .map(|mut item| {
16609                if item.data.is_none() {
16610                    item.data = Some(default_data.clone());
16611                }
16612                item
16613            })
16614            .collect::<Vec<lsp::CompletionItem>>(),
16615        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
16616    );
16617    resolved_items.lock().clear();
16618
16619    cx.update_editor(|editor, window, cx| {
16620        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
16621    });
16622    cx.run_until_parked();
16623    // Completions that have already been resolved are skipped.
16624    assert_eq!(
16625        *resolved_items.lock(),
16626        items[items.len() - 17..items.len() - 4]
16627            .iter()
16628            .cloned()
16629            .map(|mut item| {
16630                if item.data.is_none() {
16631                    item.data = Some(default_data.clone());
16632                }
16633                item
16634            })
16635            .collect::<Vec<lsp::CompletionItem>>()
16636    );
16637    resolved_items.lock().clear();
16638}
16639
16640#[gpui::test]
16641async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
16642    init_test(cx, |_| {});
16643
16644    let mut cx = EditorLspTestContext::new(
16645        Language::new(
16646            LanguageConfig {
16647                matcher: LanguageMatcher {
16648                    path_suffixes: vec!["jsx".into()],
16649                    ..Default::default()
16650                },
16651                overrides: [(
16652                    "element".into(),
16653                    LanguageConfigOverride {
16654                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
16655                        ..Default::default()
16656                    },
16657                )]
16658                .into_iter()
16659                .collect(),
16660                ..Default::default()
16661            },
16662            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
16663        )
16664        .with_override_query("(jsx_self_closing_element) @element")
16665        .unwrap(),
16666        lsp::ServerCapabilities {
16667            completion_provider: Some(lsp::CompletionOptions {
16668                trigger_characters: Some(vec![":".to_string()]),
16669                ..Default::default()
16670            }),
16671            ..Default::default()
16672        },
16673        cx,
16674    )
16675    .await;
16676
16677    cx.lsp
16678        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16679            Ok(Some(lsp::CompletionResponse::Array(vec![
16680                lsp::CompletionItem {
16681                    label: "bg-blue".into(),
16682                    ..Default::default()
16683                },
16684                lsp::CompletionItem {
16685                    label: "bg-red".into(),
16686                    ..Default::default()
16687                },
16688                lsp::CompletionItem {
16689                    label: "bg-yellow".into(),
16690                    ..Default::default()
16691                },
16692            ])))
16693        });
16694
16695    cx.set_state(r#"<p class="bgˇ" />"#);
16696
16697    // Trigger completion when typing a dash, because the dash is an extra
16698    // word character in the 'element' scope, which contains the cursor.
16699    cx.simulate_keystroke("-");
16700    cx.executor().run_until_parked();
16701    cx.update_editor(|editor, _, _| {
16702        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16703        {
16704            assert_eq!(
16705                completion_menu_entries(menu),
16706                &["bg-blue", "bg-red", "bg-yellow"]
16707            );
16708        } else {
16709            panic!("expected completion menu to be open");
16710        }
16711    });
16712
16713    cx.simulate_keystroke("l");
16714    cx.executor().run_until_parked();
16715    cx.update_editor(|editor, _, _| {
16716        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16717        {
16718            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
16719        } else {
16720            panic!("expected completion menu to be open");
16721        }
16722    });
16723
16724    // When filtering completions, consider the character after the '-' to
16725    // be the start of a subword.
16726    cx.set_state(r#"<p class="yelˇ" />"#);
16727    cx.simulate_keystroke("l");
16728    cx.executor().run_until_parked();
16729    cx.update_editor(|editor, _, _| {
16730        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16731        {
16732            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
16733        } else {
16734            panic!("expected completion menu to be open");
16735        }
16736    });
16737}
16738
16739fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
16740    let entries = menu.entries.borrow();
16741    entries.iter().map(|mat| mat.string.clone()).collect()
16742}
16743
16744#[gpui::test]
16745async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
16746    init_test(cx, |settings| {
16747        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
16748            Formatter::Prettier,
16749        )))
16750    });
16751
16752    let fs = FakeFs::new(cx.executor());
16753    fs.insert_file(path!("/file.ts"), Default::default()).await;
16754
16755    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
16756    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16757
16758    language_registry.add(Arc::new(Language::new(
16759        LanguageConfig {
16760            name: "TypeScript".into(),
16761            matcher: LanguageMatcher {
16762                path_suffixes: vec!["ts".to_string()],
16763                ..Default::default()
16764            },
16765            ..Default::default()
16766        },
16767        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
16768    )));
16769    update_test_language_settings(cx, |settings| {
16770        settings.defaults.prettier = Some(PrettierSettings {
16771            allowed: true,
16772            ..PrettierSettings::default()
16773        });
16774    });
16775
16776    let test_plugin = "test_plugin";
16777    let _ = language_registry.register_fake_lsp(
16778        "TypeScript",
16779        FakeLspAdapter {
16780            prettier_plugins: vec![test_plugin],
16781            ..Default::default()
16782        },
16783    );
16784
16785    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
16786    let buffer = project
16787        .update(cx, |project, cx| {
16788            project.open_local_buffer(path!("/file.ts"), cx)
16789        })
16790        .await
16791        .unwrap();
16792
16793    let buffer_text = "one\ntwo\nthree\n";
16794    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16795    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16796    editor.update_in(cx, |editor, window, cx| {
16797        editor.set_text(buffer_text, window, cx)
16798    });
16799
16800    editor
16801        .update_in(cx, |editor, window, cx| {
16802            editor.perform_format(
16803                project.clone(),
16804                FormatTrigger::Manual,
16805                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
16806                window,
16807                cx,
16808            )
16809        })
16810        .unwrap()
16811        .await;
16812    assert_eq!(
16813        editor.update(cx, |editor, cx| editor.text(cx)),
16814        buffer_text.to_string() + prettier_format_suffix,
16815        "Test prettier formatting was not applied to the original buffer text",
16816    );
16817
16818    update_test_language_settings(cx, |settings| {
16819        settings.defaults.formatter = Some(SelectedFormatter::Auto)
16820    });
16821    let format = editor.update_in(cx, |editor, window, cx| {
16822        editor.perform_format(
16823            project.clone(),
16824            FormatTrigger::Manual,
16825            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
16826            window,
16827            cx,
16828        )
16829    });
16830    format.await.unwrap();
16831    assert_eq!(
16832        editor.update(cx, |editor, cx| editor.text(cx)),
16833        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
16834        "Autoformatting (via test prettier) was not applied to the original buffer text",
16835    );
16836}
16837
16838#[gpui::test]
16839async fn test_addition_reverts(cx: &mut TestAppContext) {
16840    init_test(cx, |_| {});
16841    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16842    let base_text = indoc! {r#"
16843        struct Row;
16844        struct Row1;
16845        struct Row2;
16846
16847        struct Row4;
16848        struct Row5;
16849        struct Row6;
16850
16851        struct Row8;
16852        struct Row9;
16853        struct Row10;"#};
16854
16855    // When addition hunks are not adjacent to carets, no hunk revert is performed
16856    assert_hunk_revert(
16857        indoc! {r#"struct Row;
16858                   struct Row1;
16859                   struct Row1.1;
16860                   struct Row1.2;
16861                   struct Row2;ˇ
16862
16863                   struct Row4;
16864                   struct Row5;
16865                   struct Row6;
16866
16867                   struct Row8;
16868                   ˇstruct Row9;
16869                   struct Row9.1;
16870                   struct Row9.2;
16871                   struct Row9.3;
16872                   struct Row10;"#},
16873        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
16874        indoc! {r#"struct Row;
16875                   struct Row1;
16876                   struct Row1.1;
16877                   struct Row1.2;
16878                   struct Row2;ˇ
16879
16880                   struct Row4;
16881                   struct Row5;
16882                   struct Row6;
16883
16884                   struct Row8;
16885                   ˇstruct Row9;
16886                   struct Row9.1;
16887                   struct Row9.2;
16888                   struct Row9.3;
16889                   struct Row10;"#},
16890        base_text,
16891        &mut cx,
16892    );
16893    // Same for selections
16894    assert_hunk_revert(
16895        indoc! {r#"struct Row;
16896                   struct Row1;
16897                   struct Row2;
16898                   struct Row2.1;
16899                   struct Row2.2;
16900                   «ˇ
16901                   struct Row4;
16902                   struct» Row5;
16903                   «struct Row6;
16904                   ˇ»
16905                   struct Row9.1;
16906                   struct Row9.2;
16907                   struct Row9.3;
16908                   struct Row8;
16909                   struct Row9;
16910                   struct Row10;"#},
16911        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
16912        indoc! {r#"struct Row;
16913                   struct Row1;
16914                   struct Row2;
16915                   struct Row2.1;
16916                   struct Row2.2;
16917                   «ˇ
16918                   struct Row4;
16919                   struct» Row5;
16920                   «struct Row6;
16921                   ˇ»
16922                   struct Row9.1;
16923                   struct Row9.2;
16924                   struct Row9.3;
16925                   struct Row8;
16926                   struct Row9;
16927                   struct Row10;"#},
16928        base_text,
16929        &mut cx,
16930    );
16931
16932    // When carets and selections intersect the addition hunks, those are reverted.
16933    // Adjacent carets got merged.
16934    assert_hunk_revert(
16935        indoc! {r#"struct Row;
16936                   ˇ// something on the top
16937                   struct Row1;
16938                   struct Row2;
16939                   struct Roˇw3.1;
16940                   struct Row2.2;
16941                   struct Row2.3;ˇ
16942
16943                   struct Row4;
16944                   struct ˇRow5.1;
16945                   struct Row5.2;
16946                   struct «Rowˇ»5.3;
16947                   struct Row5;
16948                   struct Row6;
16949                   ˇ
16950                   struct Row9.1;
16951                   struct «Rowˇ»9.2;
16952                   struct «ˇRow»9.3;
16953                   struct Row8;
16954                   struct Row9;
16955                   «ˇ// something on bottom»
16956                   struct Row10;"#},
16957        vec![
16958            DiffHunkStatusKind::Added,
16959            DiffHunkStatusKind::Added,
16960            DiffHunkStatusKind::Added,
16961            DiffHunkStatusKind::Added,
16962            DiffHunkStatusKind::Added,
16963        ],
16964        indoc! {r#"struct Row;
16965                   ˇstruct Row1;
16966                   struct Row2;
16967                   ˇ
16968                   struct Row4;
16969                   ˇstruct Row5;
16970                   struct Row6;
16971                   ˇ
16972                   ˇstruct Row8;
16973                   struct Row9;
16974                   ˇstruct Row10;"#},
16975        base_text,
16976        &mut cx,
16977    );
16978}
16979
16980#[gpui::test]
16981async fn test_modification_reverts(cx: &mut TestAppContext) {
16982    init_test(cx, |_| {});
16983    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16984    let base_text = indoc! {r#"
16985        struct Row;
16986        struct Row1;
16987        struct Row2;
16988
16989        struct Row4;
16990        struct Row5;
16991        struct Row6;
16992
16993        struct Row8;
16994        struct Row9;
16995        struct Row10;"#};
16996
16997    // Modification hunks behave the same as the addition ones.
16998    assert_hunk_revert(
16999        indoc! {r#"struct Row;
17000                   struct Row1;
17001                   struct Row33;
17002                   ˇ
17003                   struct Row4;
17004                   struct Row5;
17005                   struct Row6;
17006                   ˇ
17007                   struct Row99;
17008                   struct Row9;
17009                   struct Row10;"#},
17010        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
17011        indoc! {r#"struct Row;
17012                   struct Row1;
17013                   struct Row33;
17014                   ˇ
17015                   struct Row4;
17016                   struct Row5;
17017                   struct Row6;
17018                   ˇ
17019                   struct Row99;
17020                   struct Row9;
17021                   struct Row10;"#},
17022        base_text,
17023        &mut cx,
17024    );
17025    assert_hunk_revert(
17026        indoc! {r#"struct Row;
17027                   struct Row1;
17028                   struct Row33;
17029                   «ˇ
17030                   struct Row4;
17031                   struct» Row5;
17032                   «struct Row6;
17033                   ˇ»
17034                   struct Row99;
17035                   struct Row9;
17036                   struct Row10;"#},
17037        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
17038        indoc! {r#"struct Row;
17039                   struct Row1;
17040                   struct Row33;
17041                   «ˇ
17042                   struct Row4;
17043                   struct» Row5;
17044                   «struct Row6;
17045                   ˇ»
17046                   struct Row99;
17047                   struct Row9;
17048                   struct Row10;"#},
17049        base_text,
17050        &mut cx,
17051    );
17052
17053    assert_hunk_revert(
17054        indoc! {r#"ˇstruct Row1.1;
17055                   struct Row1;
17056                   «ˇstr»uct Row22;
17057
17058                   struct ˇRow44;
17059                   struct Row5;
17060                   struct «Rˇ»ow66;ˇ
17061
17062                   «struˇ»ct Row88;
17063                   struct Row9;
17064                   struct Row1011;ˇ"#},
17065        vec![
17066            DiffHunkStatusKind::Modified,
17067            DiffHunkStatusKind::Modified,
17068            DiffHunkStatusKind::Modified,
17069            DiffHunkStatusKind::Modified,
17070            DiffHunkStatusKind::Modified,
17071            DiffHunkStatusKind::Modified,
17072        ],
17073        indoc! {r#"struct Row;
17074                   ˇstruct Row1;
17075                   struct Row2;
17076                   ˇ
17077                   struct Row4;
17078                   ˇstruct Row5;
17079                   struct Row6;
17080                   ˇ
17081                   struct Row8;
17082                   ˇstruct Row9;
17083                   struct Row10;ˇ"#},
17084        base_text,
17085        &mut cx,
17086    );
17087}
17088
17089#[gpui::test]
17090async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
17091    init_test(cx, |_| {});
17092    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
17093    let base_text = indoc! {r#"
17094        one
17095
17096        two
17097        three
17098        "#};
17099
17100    cx.set_head_text(base_text);
17101    cx.set_state("\nˇ\n");
17102    cx.executor().run_until_parked();
17103    cx.update_editor(|editor, _window, cx| {
17104        editor.expand_selected_diff_hunks(cx);
17105    });
17106    cx.executor().run_until_parked();
17107    cx.update_editor(|editor, window, cx| {
17108        editor.backspace(&Default::default(), window, cx);
17109    });
17110    cx.run_until_parked();
17111    cx.assert_state_with_diff(
17112        indoc! {r#"
17113
17114        - two
17115        - threeˇ
17116        +
17117        "#}
17118        .to_string(),
17119    );
17120}
17121
17122#[gpui::test]
17123async fn test_deletion_reverts(cx: &mut TestAppContext) {
17124    init_test(cx, |_| {});
17125    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
17126    let base_text = indoc! {r#"struct Row;
17127struct Row1;
17128struct Row2;
17129
17130struct Row4;
17131struct Row5;
17132struct Row6;
17133
17134struct Row8;
17135struct Row9;
17136struct Row10;"#};
17137
17138    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
17139    assert_hunk_revert(
17140        indoc! {r#"struct Row;
17141                   struct Row2;
17142
17143                   ˇstruct Row4;
17144                   struct Row5;
17145                   struct Row6;
17146                   ˇ
17147                   struct Row8;
17148                   struct Row10;"#},
17149        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
17150        indoc! {r#"struct Row;
17151                   struct Row2;
17152
17153                   ˇstruct Row4;
17154                   struct Row5;
17155                   struct Row6;
17156                   ˇ
17157                   struct Row8;
17158                   struct Row10;"#},
17159        base_text,
17160        &mut cx,
17161    );
17162    assert_hunk_revert(
17163        indoc! {r#"struct Row;
17164                   struct Row2;
17165
17166                   «ˇstruct Row4;
17167                   struct» Row5;
17168                   «struct Row6;
17169                   ˇ»
17170                   struct Row8;
17171                   struct Row10;"#},
17172        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
17173        indoc! {r#"struct Row;
17174                   struct Row2;
17175
17176                   «ˇstruct Row4;
17177                   struct» Row5;
17178                   «struct Row6;
17179                   ˇ»
17180                   struct Row8;
17181                   struct Row10;"#},
17182        base_text,
17183        &mut cx,
17184    );
17185
17186    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
17187    assert_hunk_revert(
17188        indoc! {r#"struct Row;
17189                   ˇstruct Row2;
17190
17191                   struct Row4;
17192                   struct Row5;
17193                   struct Row6;
17194
17195                   struct Row8;ˇ
17196                   struct Row10;"#},
17197        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
17198        indoc! {r#"struct Row;
17199                   struct Row1;
17200                   ˇstruct Row2;
17201
17202                   struct Row4;
17203                   struct Row5;
17204                   struct Row6;
17205
17206                   struct Row8;ˇ
17207                   struct Row9;
17208                   struct Row10;"#},
17209        base_text,
17210        &mut cx,
17211    );
17212    assert_hunk_revert(
17213        indoc! {r#"struct Row;
17214                   struct Row2«ˇ;
17215                   struct Row4;
17216                   struct» Row5;
17217                   «struct Row6;
17218
17219                   struct Row8;ˇ»
17220                   struct Row10;"#},
17221        vec![
17222            DiffHunkStatusKind::Deleted,
17223            DiffHunkStatusKind::Deleted,
17224            DiffHunkStatusKind::Deleted,
17225        ],
17226        indoc! {r#"struct Row;
17227                   struct Row1;
17228                   struct Row2«ˇ;
17229
17230                   struct Row4;
17231                   struct» Row5;
17232                   «struct Row6;
17233
17234                   struct Row8;ˇ»
17235                   struct Row9;
17236                   struct Row10;"#},
17237        base_text,
17238        &mut cx,
17239    );
17240}
17241
17242#[gpui::test]
17243async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
17244    init_test(cx, |_| {});
17245
17246    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
17247    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
17248    let base_text_3 =
17249        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
17250
17251    let text_1 = edit_first_char_of_every_line(base_text_1);
17252    let text_2 = edit_first_char_of_every_line(base_text_2);
17253    let text_3 = edit_first_char_of_every_line(base_text_3);
17254
17255    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
17256    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
17257    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
17258
17259    let multibuffer = cx.new(|cx| {
17260        let mut multibuffer = MultiBuffer::new(ReadWrite);
17261        multibuffer.push_excerpts(
17262            buffer_1.clone(),
17263            [
17264                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17265                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17266                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17267            ],
17268            cx,
17269        );
17270        multibuffer.push_excerpts(
17271            buffer_2.clone(),
17272            [
17273                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17274                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17275                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17276            ],
17277            cx,
17278        );
17279        multibuffer.push_excerpts(
17280            buffer_3.clone(),
17281            [
17282                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17283                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17284                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17285            ],
17286            cx,
17287        );
17288        multibuffer
17289    });
17290
17291    let fs = FakeFs::new(cx.executor());
17292    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
17293    let (editor, cx) = cx
17294        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
17295    editor.update_in(cx, |editor, _window, cx| {
17296        for (buffer, diff_base) in [
17297            (buffer_1.clone(), base_text_1),
17298            (buffer_2.clone(), base_text_2),
17299            (buffer_3.clone(), base_text_3),
17300        ] {
17301            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
17302            editor
17303                .buffer
17304                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
17305        }
17306    });
17307    cx.executor().run_until_parked();
17308
17309    editor.update_in(cx, |editor, window, cx| {
17310        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}");
17311        editor.select_all(&SelectAll, window, cx);
17312        editor.git_restore(&Default::default(), window, cx);
17313    });
17314    cx.executor().run_until_parked();
17315
17316    // When all ranges are selected, all buffer hunks are reverted.
17317    editor.update(cx, |editor, cx| {
17318        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");
17319    });
17320    buffer_1.update(cx, |buffer, _| {
17321        assert_eq!(buffer.text(), base_text_1);
17322    });
17323    buffer_2.update(cx, |buffer, _| {
17324        assert_eq!(buffer.text(), base_text_2);
17325    });
17326    buffer_3.update(cx, |buffer, _| {
17327        assert_eq!(buffer.text(), base_text_3);
17328    });
17329
17330    editor.update_in(cx, |editor, window, cx| {
17331        editor.undo(&Default::default(), window, cx);
17332    });
17333
17334    editor.update_in(cx, |editor, window, cx| {
17335        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17336            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
17337        });
17338        editor.git_restore(&Default::default(), window, cx);
17339    });
17340
17341    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
17342    // but not affect buffer_2 and its related excerpts.
17343    editor.update(cx, |editor, cx| {
17344        assert_eq!(
17345            editor.text(cx),
17346            "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}"
17347        );
17348    });
17349    buffer_1.update(cx, |buffer, _| {
17350        assert_eq!(buffer.text(), base_text_1);
17351    });
17352    buffer_2.update(cx, |buffer, _| {
17353        assert_eq!(
17354            buffer.text(),
17355            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
17356        );
17357    });
17358    buffer_3.update(cx, |buffer, _| {
17359        assert_eq!(
17360            buffer.text(),
17361            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
17362        );
17363    });
17364
17365    fn edit_first_char_of_every_line(text: &str) -> String {
17366        text.split('\n')
17367            .map(|line| format!("X{}", &line[1..]))
17368            .collect::<Vec<_>>()
17369            .join("\n")
17370    }
17371}
17372
17373#[gpui::test]
17374async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
17375    init_test(cx, |_| {});
17376
17377    let cols = 4;
17378    let rows = 10;
17379    let sample_text_1 = sample_text(rows, cols, 'a');
17380    assert_eq!(
17381        sample_text_1,
17382        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
17383    );
17384    let sample_text_2 = sample_text(rows, cols, 'l');
17385    assert_eq!(
17386        sample_text_2,
17387        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
17388    );
17389    let sample_text_3 = sample_text(rows, cols, 'v');
17390    assert_eq!(
17391        sample_text_3,
17392        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
17393    );
17394
17395    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
17396    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
17397    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
17398
17399    let multi_buffer = cx.new(|cx| {
17400        let mut multibuffer = MultiBuffer::new(ReadWrite);
17401        multibuffer.push_excerpts(
17402            buffer_1.clone(),
17403            [
17404                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17405                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17406                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17407            ],
17408            cx,
17409        );
17410        multibuffer.push_excerpts(
17411            buffer_2.clone(),
17412            [
17413                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17414                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17415                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17416            ],
17417            cx,
17418        );
17419        multibuffer.push_excerpts(
17420            buffer_3.clone(),
17421            [
17422                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17423                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17424                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17425            ],
17426            cx,
17427        );
17428        multibuffer
17429    });
17430
17431    let fs = FakeFs::new(cx.executor());
17432    fs.insert_tree(
17433        "/a",
17434        json!({
17435            "main.rs": sample_text_1,
17436            "other.rs": sample_text_2,
17437            "lib.rs": sample_text_3,
17438        }),
17439    )
17440    .await;
17441    let project = Project::test(fs, ["/a".as_ref()], cx).await;
17442    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17443    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17444    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
17445        Editor::new(
17446            EditorMode::full(),
17447            multi_buffer,
17448            Some(project.clone()),
17449            window,
17450            cx,
17451        )
17452    });
17453    let multibuffer_item_id = workspace
17454        .update(cx, |workspace, window, cx| {
17455            assert!(
17456                workspace.active_item(cx).is_none(),
17457                "active item should be None before the first item is added"
17458            );
17459            workspace.add_item_to_active_pane(
17460                Box::new(multi_buffer_editor.clone()),
17461                None,
17462                true,
17463                window,
17464                cx,
17465            );
17466            let active_item = workspace
17467                .active_item(cx)
17468                .expect("should have an active item after adding the multi buffer");
17469            assert!(
17470                !active_item.is_singleton(cx),
17471                "A multi buffer was expected to active after adding"
17472            );
17473            active_item.item_id()
17474        })
17475        .unwrap();
17476    cx.executor().run_until_parked();
17477
17478    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17479        editor.change_selections(
17480            SelectionEffects::scroll(Autoscroll::Next),
17481            window,
17482            cx,
17483            |s| s.select_ranges(Some(1..2)),
17484        );
17485        editor.open_excerpts(&OpenExcerpts, window, cx);
17486    });
17487    cx.executor().run_until_parked();
17488    let first_item_id = workspace
17489        .update(cx, |workspace, window, cx| {
17490            let active_item = workspace
17491                .active_item(cx)
17492                .expect("should have an active item after navigating into the 1st buffer");
17493            let first_item_id = active_item.item_id();
17494            assert_ne!(
17495                first_item_id, multibuffer_item_id,
17496                "Should navigate into the 1st buffer and activate it"
17497            );
17498            assert!(
17499                active_item.is_singleton(cx),
17500                "New active item should be a singleton buffer"
17501            );
17502            assert_eq!(
17503                active_item
17504                    .act_as::<Editor>(cx)
17505                    .expect("should have navigated into an editor for the 1st buffer")
17506                    .read(cx)
17507                    .text(cx),
17508                sample_text_1
17509            );
17510
17511            workspace
17512                .go_back(workspace.active_pane().downgrade(), window, cx)
17513                .detach_and_log_err(cx);
17514
17515            first_item_id
17516        })
17517        .unwrap();
17518    cx.executor().run_until_parked();
17519    workspace
17520        .update(cx, |workspace, _, cx| {
17521            let active_item = workspace
17522                .active_item(cx)
17523                .expect("should have an active item after navigating back");
17524            assert_eq!(
17525                active_item.item_id(),
17526                multibuffer_item_id,
17527                "Should navigate back to the multi buffer"
17528            );
17529            assert!(!active_item.is_singleton(cx));
17530        })
17531        .unwrap();
17532
17533    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17534        editor.change_selections(
17535            SelectionEffects::scroll(Autoscroll::Next),
17536            window,
17537            cx,
17538            |s| s.select_ranges(Some(39..40)),
17539        );
17540        editor.open_excerpts(&OpenExcerpts, window, cx);
17541    });
17542    cx.executor().run_until_parked();
17543    let second_item_id = workspace
17544        .update(cx, |workspace, window, cx| {
17545            let active_item = workspace
17546                .active_item(cx)
17547                .expect("should have an active item after navigating into the 2nd buffer");
17548            let second_item_id = active_item.item_id();
17549            assert_ne!(
17550                second_item_id, multibuffer_item_id,
17551                "Should navigate away from the multibuffer"
17552            );
17553            assert_ne!(
17554                second_item_id, first_item_id,
17555                "Should navigate into the 2nd buffer and activate it"
17556            );
17557            assert!(
17558                active_item.is_singleton(cx),
17559                "New active item should be a singleton buffer"
17560            );
17561            assert_eq!(
17562                active_item
17563                    .act_as::<Editor>(cx)
17564                    .expect("should have navigated into an editor")
17565                    .read(cx)
17566                    .text(cx),
17567                sample_text_2
17568            );
17569
17570            workspace
17571                .go_back(workspace.active_pane().downgrade(), window, cx)
17572                .detach_and_log_err(cx);
17573
17574            second_item_id
17575        })
17576        .unwrap();
17577    cx.executor().run_until_parked();
17578    workspace
17579        .update(cx, |workspace, _, cx| {
17580            let active_item = workspace
17581                .active_item(cx)
17582                .expect("should have an active item after navigating back from the 2nd buffer");
17583            assert_eq!(
17584                active_item.item_id(),
17585                multibuffer_item_id,
17586                "Should navigate back from the 2nd buffer to the multi buffer"
17587            );
17588            assert!(!active_item.is_singleton(cx));
17589        })
17590        .unwrap();
17591
17592    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17593        editor.change_selections(
17594            SelectionEffects::scroll(Autoscroll::Next),
17595            window,
17596            cx,
17597            |s| s.select_ranges(Some(70..70)),
17598        );
17599        editor.open_excerpts(&OpenExcerpts, window, cx);
17600    });
17601    cx.executor().run_until_parked();
17602    workspace
17603        .update(cx, |workspace, window, cx| {
17604            let active_item = workspace
17605                .active_item(cx)
17606                .expect("should have an active item after navigating into the 3rd buffer");
17607            let third_item_id = active_item.item_id();
17608            assert_ne!(
17609                third_item_id, multibuffer_item_id,
17610                "Should navigate into the 3rd buffer and activate it"
17611            );
17612            assert_ne!(third_item_id, first_item_id);
17613            assert_ne!(third_item_id, second_item_id);
17614            assert!(
17615                active_item.is_singleton(cx),
17616                "New active item should be a singleton buffer"
17617            );
17618            assert_eq!(
17619                active_item
17620                    .act_as::<Editor>(cx)
17621                    .expect("should have navigated into an editor")
17622                    .read(cx)
17623                    .text(cx),
17624                sample_text_3
17625            );
17626
17627            workspace
17628                .go_back(workspace.active_pane().downgrade(), window, cx)
17629                .detach_and_log_err(cx);
17630        })
17631        .unwrap();
17632    cx.executor().run_until_parked();
17633    workspace
17634        .update(cx, |workspace, _, cx| {
17635            let active_item = workspace
17636                .active_item(cx)
17637                .expect("should have an active item after navigating back from the 3rd buffer");
17638            assert_eq!(
17639                active_item.item_id(),
17640                multibuffer_item_id,
17641                "Should navigate back from the 3rd buffer to the multi buffer"
17642            );
17643            assert!(!active_item.is_singleton(cx));
17644        })
17645        .unwrap();
17646}
17647
17648#[gpui::test]
17649async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17650    init_test(cx, |_| {});
17651
17652    let mut cx = EditorTestContext::new(cx).await;
17653
17654    let diff_base = r#"
17655        use some::mod;
17656
17657        const A: u32 = 42;
17658
17659        fn main() {
17660            println!("hello");
17661
17662            println!("world");
17663        }
17664        "#
17665    .unindent();
17666
17667    cx.set_state(
17668        &r#"
17669        use some::modified;
17670
17671        ˇ
17672        fn main() {
17673            println!("hello there");
17674
17675            println!("around the");
17676            println!("world");
17677        }
17678        "#
17679        .unindent(),
17680    );
17681
17682    cx.set_head_text(&diff_base);
17683    executor.run_until_parked();
17684
17685    cx.update_editor(|editor, window, cx| {
17686        editor.go_to_next_hunk(&GoToHunk, window, cx);
17687        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17688    });
17689    executor.run_until_parked();
17690    cx.assert_state_with_diff(
17691        r#"
17692          use some::modified;
17693
17694
17695          fn main() {
17696        -     println!("hello");
17697        + ˇ    println!("hello there");
17698
17699              println!("around the");
17700              println!("world");
17701          }
17702        "#
17703        .unindent(),
17704    );
17705
17706    cx.update_editor(|editor, window, cx| {
17707        for _ in 0..2 {
17708            editor.go_to_next_hunk(&GoToHunk, window, cx);
17709            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17710        }
17711    });
17712    executor.run_until_parked();
17713    cx.assert_state_with_diff(
17714        r#"
17715        - use some::mod;
17716        + ˇuse some::modified;
17717
17718
17719          fn main() {
17720        -     println!("hello");
17721        +     println!("hello there");
17722
17723        +     println!("around the");
17724              println!("world");
17725          }
17726        "#
17727        .unindent(),
17728    );
17729
17730    cx.update_editor(|editor, window, cx| {
17731        editor.go_to_next_hunk(&GoToHunk, window, cx);
17732        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17733    });
17734    executor.run_until_parked();
17735    cx.assert_state_with_diff(
17736        r#"
17737        - use some::mod;
17738        + use some::modified;
17739
17740        - const A: u32 = 42;
17741          ˇ
17742          fn main() {
17743        -     println!("hello");
17744        +     println!("hello there");
17745
17746        +     println!("around the");
17747              println!("world");
17748          }
17749        "#
17750        .unindent(),
17751    );
17752
17753    cx.update_editor(|editor, window, cx| {
17754        editor.cancel(&Cancel, window, cx);
17755    });
17756
17757    cx.assert_state_with_diff(
17758        r#"
17759          use some::modified;
17760
17761          ˇ
17762          fn main() {
17763              println!("hello there");
17764
17765              println!("around the");
17766              println!("world");
17767          }
17768        "#
17769        .unindent(),
17770    );
17771}
17772
17773#[gpui::test]
17774async fn test_diff_base_change_with_expanded_diff_hunks(
17775    executor: BackgroundExecutor,
17776    cx: &mut TestAppContext,
17777) {
17778    init_test(cx, |_| {});
17779
17780    let mut cx = EditorTestContext::new(cx).await;
17781
17782    let diff_base = r#"
17783        use some::mod1;
17784        use some::mod2;
17785
17786        const A: u32 = 42;
17787        const B: u32 = 42;
17788        const C: u32 = 42;
17789
17790        fn main() {
17791            println!("hello");
17792
17793            println!("world");
17794        }
17795        "#
17796    .unindent();
17797
17798    cx.set_state(
17799        &r#"
17800        use some::mod2;
17801
17802        const A: u32 = 42;
17803        const C: u32 = 42;
17804
17805        fn main(ˇ) {
17806            //println!("hello");
17807
17808            println!("world");
17809            //
17810            //
17811        }
17812        "#
17813        .unindent(),
17814    );
17815
17816    cx.set_head_text(&diff_base);
17817    executor.run_until_parked();
17818
17819    cx.update_editor(|editor, window, cx| {
17820        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17821    });
17822    executor.run_until_parked();
17823    cx.assert_state_with_diff(
17824        r#"
17825        - use some::mod1;
17826          use some::mod2;
17827
17828          const A: u32 = 42;
17829        - const B: u32 = 42;
17830          const C: u32 = 42;
17831
17832          fn main(ˇ) {
17833        -     println!("hello");
17834        +     //println!("hello");
17835
17836              println!("world");
17837        +     //
17838        +     //
17839          }
17840        "#
17841        .unindent(),
17842    );
17843
17844    cx.set_head_text("new diff base!");
17845    executor.run_until_parked();
17846    cx.assert_state_with_diff(
17847        r#"
17848        - new diff base!
17849        + use some::mod2;
17850        +
17851        + const A: u32 = 42;
17852        + const C: u32 = 42;
17853        +
17854        + fn main(ˇ) {
17855        +     //println!("hello");
17856        +
17857        +     println!("world");
17858        +     //
17859        +     //
17860        + }
17861        "#
17862        .unindent(),
17863    );
17864}
17865
17866#[gpui::test]
17867async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
17868    init_test(cx, |_| {});
17869
17870    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
17871    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
17872    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
17873    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
17874    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
17875    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
17876
17877    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
17878    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
17879    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
17880
17881    let multi_buffer = cx.new(|cx| {
17882        let mut multibuffer = MultiBuffer::new(ReadWrite);
17883        multibuffer.push_excerpts(
17884            buffer_1.clone(),
17885            [
17886                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17887                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17888                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17889            ],
17890            cx,
17891        );
17892        multibuffer.push_excerpts(
17893            buffer_2.clone(),
17894            [
17895                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17896                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17897                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17898            ],
17899            cx,
17900        );
17901        multibuffer.push_excerpts(
17902            buffer_3.clone(),
17903            [
17904                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17905                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17906                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17907            ],
17908            cx,
17909        );
17910        multibuffer
17911    });
17912
17913    let editor =
17914        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
17915    editor
17916        .update(cx, |editor, _window, cx| {
17917            for (buffer, diff_base) in [
17918                (buffer_1.clone(), file_1_old),
17919                (buffer_2.clone(), file_2_old),
17920                (buffer_3.clone(), file_3_old),
17921            ] {
17922                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
17923                editor
17924                    .buffer
17925                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
17926            }
17927        })
17928        .unwrap();
17929
17930    let mut cx = EditorTestContext::for_editor(editor, cx).await;
17931    cx.run_until_parked();
17932
17933    cx.assert_editor_state(
17934        &"
17935            ˇaaa
17936            ccc
17937            ddd
17938
17939            ggg
17940            hhh
17941
17942
17943            lll
17944            mmm
17945            NNN
17946
17947            qqq
17948            rrr
17949
17950            uuu
17951            111
17952            222
17953            333
17954
17955            666
17956            777
17957
17958            000
17959            !!!"
17960        .unindent(),
17961    );
17962
17963    cx.update_editor(|editor, window, cx| {
17964        editor.select_all(&SelectAll, window, cx);
17965        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17966    });
17967    cx.executor().run_until_parked();
17968
17969    cx.assert_state_with_diff(
17970        "
17971            «aaa
17972          - bbb
17973            ccc
17974            ddd
17975
17976            ggg
17977            hhh
17978
17979
17980            lll
17981            mmm
17982          - nnn
17983          + NNN
17984
17985            qqq
17986            rrr
17987
17988            uuu
17989            111
17990            222
17991            333
17992
17993          + 666
17994            777
17995
17996            000
17997            !!!ˇ»"
17998            .unindent(),
17999    );
18000}
18001
18002#[gpui::test]
18003async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
18004    init_test(cx, |_| {});
18005
18006    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
18007    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
18008
18009    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
18010    let multi_buffer = cx.new(|cx| {
18011        let mut multibuffer = MultiBuffer::new(ReadWrite);
18012        multibuffer.push_excerpts(
18013            buffer.clone(),
18014            [
18015                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
18016                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
18017                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
18018            ],
18019            cx,
18020        );
18021        multibuffer
18022    });
18023
18024    let editor =
18025        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
18026    editor
18027        .update(cx, |editor, _window, cx| {
18028            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
18029            editor
18030                .buffer
18031                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
18032        })
18033        .unwrap();
18034
18035    let mut cx = EditorTestContext::for_editor(editor, cx).await;
18036    cx.run_until_parked();
18037
18038    cx.update_editor(|editor, window, cx| {
18039        editor.expand_all_diff_hunks(&Default::default(), window, cx)
18040    });
18041    cx.executor().run_until_parked();
18042
18043    // When the start of a hunk coincides with the start of its excerpt,
18044    // the hunk is expanded. When the start of a a hunk is earlier than
18045    // the start of its excerpt, the hunk is not expanded.
18046    cx.assert_state_with_diff(
18047        "
18048            ˇaaa
18049          - bbb
18050          + BBB
18051
18052          - ddd
18053          - eee
18054          + DDD
18055          + EEE
18056            fff
18057
18058            iii
18059        "
18060        .unindent(),
18061    );
18062}
18063
18064#[gpui::test]
18065async fn test_edits_around_expanded_insertion_hunks(
18066    executor: BackgroundExecutor,
18067    cx: &mut TestAppContext,
18068) {
18069    init_test(cx, |_| {});
18070
18071    let mut cx = EditorTestContext::new(cx).await;
18072
18073    let diff_base = r#"
18074        use some::mod1;
18075        use some::mod2;
18076
18077        const A: u32 = 42;
18078
18079        fn main() {
18080            println!("hello");
18081
18082            println!("world");
18083        }
18084        "#
18085    .unindent();
18086    executor.run_until_parked();
18087    cx.set_state(
18088        &r#"
18089        use some::mod1;
18090        use some::mod2;
18091
18092        const A: u32 = 42;
18093        const B: u32 = 42;
18094        const C: u32 = 42;
18095        ˇ
18096
18097        fn main() {
18098            println!("hello");
18099
18100            println!("world");
18101        }
18102        "#
18103        .unindent(),
18104    );
18105
18106    cx.set_head_text(&diff_base);
18107    executor.run_until_parked();
18108
18109    cx.update_editor(|editor, window, cx| {
18110        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18111    });
18112    executor.run_until_parked();
18113
18114    cx.assert_state_with_diff(
18115        r#"
18116        use some::mod1;
18117        use some::mod2;
18118
18119        const A: u32 = 42;
18120      + const B: u32 = 42;
18121      + const C: u32 = 42;
18122      + ˇ
18123
18124        fn main() {
18125            println!("hello");
18126
18127            println!("world");
18128        }
18129      "#
18130        .unindent(),
18131    );
18132
18133    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
18134    executor.run_until_parked();
18135
18136    cx.assert_state_with_diff(
18137        r#"
18138        use some::mod1;
18139        use some::mod2;
18140
18141        const A: u32 = 42;
18142      + const B: u32 = 42;
18143      + const C: u32 = 42;
18144      + const D: u32 = 42;
18145      + ˇ
18146
18147        fn main() {
18148            println!("hello");
18149
18150            println!("world");
18151        }
18152      "#
18153        .unindent(),
18154    );
18155
18156    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
18157    executor.run_until_parked();
18158
18159    cx.assert_state_with_diff(
18160        r#"
18161        use some::mod1;
18162        use some::mod2;
18163
18164        const A: u32 = 42;
18165      + const B: u32 = 42;
18166      + const C: u32 = 42;
18167      + const D: u32 = 42;
18168      + const E: u32 = 42;
18169      + ˇ
18170
18171        fn main() {
18172            println!("hello");
18173
18174            println!("world");
18175        }
18176      "#
18177        .unindent(),
18178    );
18179
18180    cx.update_editor(|editor, window, cx| {
18181        editor.delete_line(&DeleteLine, window, cx);
18182    });
18183    executor.run_until_parked();
18184
18185    cx.assert_state_with_diff(
18186        r#"
18187        use some::mod1;
18188        use some::mod2;
18189
18190        const A: u32 = 42;
18191      + const B: u32 = 42;
18192      + const C: u32 = 42;
18193      + const D: u32 = 42;
18194      + const E: u32 = 42;
18195        ˇ
18196        fn main() {
18197            println!("hello");
18198
18199            println!("world");
18200        }
18201      "#
18202        .unindent(),
18203    );
18204
18205    cx.update_editor(|editor, window, cx| {
18206        editor.move_up(&MoveUp, window, cx);
18207        editor.delete_line(&DeleteLine, window, cx);
18208        editor.move_up(&MoveUp, window, cx);
18209        editor.delete_line(&DeleteLine, window, cx);
18210        editor.move_up(&MoveUp, window, cx);
18211        editor.delete_line(&DeleteLine, window, cx);
18212    });
18213    executor.run_until_parked();
18214    cx.assert_state_with_diff(
18215        r#"
18216        use some::mod1;
18217        use some::mod2;
18218
18219        const A: u32 = 42;
18220      + const B: u32 = 42;
18221        ˇ
18222        fn main() {
18223            println!("hello");
18224
18225            println!("world");
18226        }
18227      "#
18228        .unindent(),
18229    );
18230
18231    cx.update_editor(|editor, window, cx| {
18232        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
18233        editor.delete_line(&DeleteLine, window, cx);
18234    });
18235    executor.run_until_parked();
18236    cx.assert_state_with_diff(
18237        r#"
18238        ˇ
18239        fn main() {
18240            println!("hello");
18241
18242            println!("world");
18243        }
18244      "#
18245        .unindent(),
18246    );
18247}
18248
18249#[gpui::test]
18250async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
18251    init_test(cx, |_| {});
18252
18253    let mut cx = EditorTestContext::new(cx).await;
18254    cx.set_head_text(indoc! { "
18255        one
18256        two
18257        three
18258        four
18259        five
18260        "
18261    });
18262    cx.set_state(indoc! { "
18263        one
18264        ˇthree
18265        five
18266    "});
18267    cx.run_until_parked();
18268    cx.update_editor(|editor, window, cx| {
18269        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
18270    });
18271    cx.assert_state_with_diff(
18272        indoc! { "
18273        one
18274      - two
18275        ˇthree
18276      - four
18277        five
18278    "}
18279        .to_string(),
18280    );
18281    cx.update_editor(|editor, window, cx| {
18282        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
18283    });
18284
18285    cx.assert_state_with_diff(
18286        indoc! { "
18287        one
18288        ˇthree
18289        five
18290    "}
18291        .to_string(),
18292    );
18293
18294    cx.set_state(indoc! { "
18295        one
18296        ˇTWO
18297        three
18298        four
18299        five
18300    "});
18301    cx.run_until_parked();
18302    cx.update_editor(|editor, window, cx| {
18303        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
18304    });
18305
18306    cx.assert_state_with_diff(
18307        indoc! { "
18308            one
18309          - two
18310          + ˇTWO
18311            three
18312            four
18313            five
18314        "}
18315        .to_string(),
18316    );
18317    cx.update_editor(|editor, window, cx| {
18318        editor.move_up(&Default::default(), window, cx);
18319        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
18320    });
18321    cx.assert_state_with_diff(
18322        indoc! { "
18323            one
18324            ˇTWO
18325            three
18326            four
18327            five
18328        "}
18329        .to_string(),
18330    );
18331}
18332
18333#[gpui::test]
18334async fn test_edits_around_expanded_deletion_hunks(
18335    executor: BackgroundExecutor,
18336    cx: &mut TestAppContext,
18337) {
18338    init_test(cx, |_| {});
18339
18340    let mut cx = EditorTestContext::new(cx).await;
18341
18342    let diff_base = r#"
18343        use some::mod1;
18344        use some::mod2;
18345
18346        const A: u32 = 42;
18347        const B: u32 = 42;
18348        const C: u32 = 42;
18349
18350
18351        fn main() {
18352            println!("hello");
18353
18354            println!("world");
18355        }
18356    "#
18357    .unindent();
18358    executor.run_until_parked();
18359    cx.set_state(
18360        &r#"
18361        use some::mod1;
18362        use some::mod2;
18363
18364        ˇconst B: u32 = 42;
18365        const C: u32 = 42;
18366
18367
18368        fn main() {
18369            println!("hello");
18370
18371            println!("world");
18372        }
18373        "#
18374        .unindent(),
18375    );
18376
18377    cx.set_head_text(&diff_base);
18378    executor.run_until_parked();
18379
18380    cx.update_editor(|editor, window, cx| {
18381        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18382    });
18383    executor.run_until_parked();
18384
18385    cx.assert_state_with_diff(
18386        r#"
18387        use some::mod1;
18388        use some::mod2;
18389
18390      - const A: u32 = 42;
18391        ˇconst B: u32 = 42;
18392        const C: u32 = 42;
18393
18394
18395        fn main() {
18396            println!("hello");
18397
18398            println!("world");
18399        }
18400      "#
18401        .unindent(),
18402    );
18403
18404    cx.update_editor(|editor, window, cx| {
18405        editor.delete_line(&DeleteLine, window, cx);
18406    });
18407    executor.run_until_parked();
18408    cx.assert_state_with_diff(
18409        r#"
18410        use some::mod1;
18411        use some::mod2;
18412
18413      - const A: u32 = 42;
18414      - const B: u32 = 42;
18415        ˇconst C: u32 = 42;
18416
18417
18418        fn main() {
18419            println!("hello");
18420
18421            println!("world");
18422        }
18423      "#
18424        .unindent(),
18425    );
18426
18427    cx.update_editor(|editor, window, cx| {
18428        editor.delete_line(&DeleteLine, window, cx);
18429    });
18430    executor.run_until_parked();
18431    cx.assert_state_with_diff(
18432        r#"
18433        use some::mod1;
18434        use some::mod2;
18435
18436      - const A: u32 = 42;
18437      - const B: u32 = 42;
18438      - const C: u32 = 42;
18439        ˇ
18440
18441        fn main() {
18442            println!("hello");
18443
18444            println!("world");
18445        }
18446      "#
18447        .unindent(),
18448    );
18449
18450    cx.update_editor(|editor, window, cx| {
18451        editor.handle_input("replacement", window, cx);
18452    });
18453    executor.run_until_parked();
18454    cx.assert_state_with_diff(
18455        r#"
18456        use some::mod1;
18457        use some::mod2;
18458
18459      - const A: u32 = 42;
18460      - const B: u32 = 42;
18461      - const C: u32 = 42;
18462      -
18463      + replacementˇ
18464
18465        fn main() {
18466            println!("hello");
18467
18468            println!("world");
18469        }
18470      "#
18471        .unindent(),
18472    );
18473}
18474
18475#[gpui::test]
18476async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18477    init_test(cx, |_| {});
18478
18479    let mut cx = EditorTestContext::new(cx).await;
18480
18481    let base_text = r#"
18482        one
18483        two
18484        three
18485        four
18486        five
18487    "#
18488    .unindent();
18489    executor.run_until_parked();
18490    cx.set_state(
18491        &r#"
18492        one
18493        two
18494        fˇour
18495        five
18496        "#
18497        .unindent(),
18498    );
18499
18500    cx.set_head_text(&base_text);
18501    executor.run_until_parked();
18502
18503    cx.update_editor(|editor, window, cx| {
18504        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18505    });
18506    executor.run_until_parked();
18507
18508    cx.assert_state_with_diff(
18509        r#"
18510          one
18511          two
18512        - three
18513          fˇour
18514          five
18515        "#
18516        .unindent(),
18517    );
18518
18519    cx.update_editor(|editor, window, cx| {
18520        editor.backspace(&Backspace, window, cx);
18521        editor.backspace(&Backspace, window, cx);
18522    });
18523    executor.run_until_parked();
18524    cx.assert_state_with_diff(
18525        r#"
18526          one
18527          two
18528        - threeˇ
18529        - four
18530        + our
18531          five
18532        "#
18533        .unindent(),
18534    );
18535}
18536
18537#[gpui::test]
18538async fn test_edit_after_expanded_modification_hunk(
18539    executor: BackgroundExecutor,
18540    cx: &mut TestAppContext,
18541) {
18542    init_test(cx, |_| {});
18543
18544    let mut cx = EditorTestContext::new(cx).await;
18545
18546    let diff_base = r#"
18547        use some::mod1;
18548        use some::mod2;
18549
18550        const A: u32 = 42;
18551        const B: u32 = 42;
18552        const C: u32 = 42;
18553        const D: u32 = 42;
18554
18555
18556        fn main() {
18557            println!("hello");
18558
18559            println!("world");
18560        }"#
18561    .unindent();
18562
18563    cx.set_state(
18564        &r#"
18565        use some::mod1;
18566        use some::mod2;
18567
18568        const A: u32 = 42;
18569        const B: u32 = 42;
18570        const C: u32 = 43ˇ
18571        const D: u32 = 42;
18572
18573
18574        fn main() {
18575            println!("hello");
18576
18577            println!("world");
18578        }"#
18579        .unindent(),
18580    );
18581
18582    cx.set_head_text(&diff_base);
18583    executor.run_until_parked();
18584    cx.update_editor(|editor, window, cx| {
18585        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18586    });
18587    executor.run_until_parked();
18588
18589    cx.assert_state_with_diff(
18590        r#"
18591        use some::mod1;
18592        use some::mod2;
18593
18594        const A: u32 = 42;
18595        const B: u32 = 42;
18596      - const C: u32 = 42;
18597      + const C: u32 = 43ˇ
18598        const D: u32 = 42;
18599
18600
18601        fn main() {
18602            println!("hello");
18603
18604            println!("world");
18605        }"#
18606        .unindent(),
18607    );
18608
18609    cx.update_editor(|editor, window, cx| {
18610        editor.handle_input("\nnew_line\n", window, cx);
18611    });
18612    executor.run_until_parked();
18613
18614    cx.assert_state_with_diff(
18615        r#"
18616        use some::mod1;
18617        use some::mod2;
18618
18619        const A: u32 = 42;
18620        const B: u32 = 42;
18621      - const C: u32 = 42;
18622      + const C: u32 = 43
18623      + new_line
18624      + ˇ
18625        const D: u32 = 42;
18626
18627
18628        fn main() {
18629            println!("hello");
18630
18631            println!("world");
18632        }"#
18633        .unindent(),
18634    );
18635}
18636
18637#[gpui::test]
18638async fn test_stage_and_unstage_added_file_hunk(
18639    executor: BackgroundExecutor,
18640    cx: &mut TestAppContext,
18641) {
18642    init_test(cx, |_| {});
18643
18644    let mut cx = EditorTestContext::new(cx).await;
18645    cx.update_editor(|editor, _, cx| {
18646        editor.set_expand_all_diff_hunks(cx);
18647    });
18648
18649    let working_copy = r#"
18650            ˇfn main() {
18651                println!("hello, world!");
18652            }
18653        "#
18654    .unindent();
18655
18656    cx.set_state(&working_copy);
18657    executor.run_until_parked();
18658
18659    cx.assert_state_with_diff(
18660        r#"
18661            + ˇfn main() {
18662            +     println!("hello, world!");
18663            + }
18664        "#
18665        .unindent(),
18666    );
18667    cx.assert_index_text(None);
18668
18669    cx.update_editor(|editor, window, cx| {
18670        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18671    });
18672    executor.run_until_parked();
18673    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
18674    cx.assert_state_with_diff(
18675        r#"
18676            + ˇfn main() {
18677            +     println!("hello, world!");
18678            + }
18679        "#
18680        .unindent(),
18681    );
18682
18683    cx.update_editor(|editor, window, cx| {
18684        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18685    });
18686    executor.run_until_parked();
18687    cx.assert_index_text(None);
18688}
18689
18690async fn setup_indent_guides_editor(
18691    text: &str,
18692    cx: &mut TestAppContext,
18693) -> (BufferId, EditorTestContext) {
18694    init_test(cx, |_| {});
18695
18696    let mut cx = EditorTestContext::new(cx).await;
18697
18698    let buffer_id = cx.update_editor(|editor, window, cx| {
18699        editor.set_text(text, window, cx);
18700        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
18701
18702        buffer_ids[0]
18703    });
18704
18705    (buffer_id, cx)
18706}
18707
18708fn assert_indent_guides(
18709    range: Range<u32>,
18710    expected: Vec<IndentGuide>,
18711    active_indices: Option<Vec<usize>>,
18712    cx: &mut EditorTestContext,
18713) {
18714    let indent_guides = cx.update_editor(|editor, window, cx| {
18715        let snapshot = editor.snapshot(window, cx).display_snapshot;
18716        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
18717            editor,
18718            MultiBufferRow(range.start)..MultiBufferRow(range.end),
18719            true,
18720            &snapshot,
18721            cx,
18722        );
18723
18724        indent_guides.sort_by(|a, b| {
18725            a.depth.cmp(&b.depth).then(
18726                a.start_row
18727                    .cmp(&b.start_row)
18728                    .then(a.end_row.cmp(&b.end_row)),
18729            )
18730        });
18731        indent_guides
18732    });
18733
18734    if let Some(expected) = active_indices {
18735        let active_indices = cx.update_editor(|editor, window, cx| {
18736            let snapshot = editor.snapshot(window, cx).display_snapshot;
18737            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
18738        });
18739
18740        assert_eq!(
18741            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
18742            expected,
18743            "Active indent guide indices do not match"
18744        );
18745    }
18746
18747    assert_eq!(indent_guides, expected, "Indent guides do not match");
18748}
18749
18750fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
18751    IndentGuide {
18752        buffer_id,
18753        start_row: MultiBufferRow(start_row),
18754        end_row: MultiBufferRow(end_row),
18755        depth,
18756        tab_size: 4,
18757        settings: IndentGuideSettings {
18758            enabled: true,
18759            line_width: 1,
18760            active_line_width: 1,
18761            ..Default::default()
18762        },
18763    }
18764}
18765
18766#[gpui::test]
18767async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
18768    let (buffer_id, mut cx) = setup_indent_guides_editor(
18769        &"
18770        fn main() {
18771            let a = 1;
18772        }"
18773        .unindent(),
18774        cx,
18775    )
18776    .await;
18777
18778    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
18779}
18780
18781#[gpui::test]
18782async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
18783    let (buffer_id, mut cx) = setup_indent_guides_editor(
18784        &"
18785        fn main() {
18786            let a = 1;
18787            let b = 2;
18788        }"
18789        .unindent(),
18790        cx,
18791    )
18792    .await;
18793
18794    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
18795}
18796
18797#[gpui::test]
18798async fn test_indent_guide_nested(cx: &mut TestAppContext) {
18799    let (buffer_id, mut cx) = setup_indent_guides_editor(
18800        &"
18801        fn main() {
18802            let a = 1;
18803            if a == 3 {
18804                let b = 2;
18805            } else {
18806                let c = 3;
18807            }
18808        }"
18809        .unindent(),
18810        cx,
18811    )
18812    .await;
18813
18814    assert_indent_guides(
18815        0..8,
18816        vec![
18817            indent_guide(buffer_id, 1, 6, 0),
18818            indent_guide(buffer_id, 3, 3, 1),
18819            indent_guide(buffer_id, 5, 5, 1),
18820        ],
18821        None,
18822        &mut cx,
18823    );
18824}
18825
18826#[gpui::test]
18827async fn test_indent_guide_tab(cx: &mut TestAppContext) {
18828    let (buffer_id, mut cx) = setup_indent_guides_editor(
18829        &"
18830        fn main() {
18831            let a = 1;
18832                let b = 2;
18833            let c = 3;
18834        }"
18835        .unindent(),
18836        cx,
18837    )
18838    .await;
18839
18840    assert_indent_guides(
18841        0..5,
18842        vec![
18843            indent_guide(buffer_id, 1, 3, 0),
18844            indent_guide(buffer_id, 2, 2, 1),
18845        ],
18846        None,
18847        &mut cx,
18848    );
18849}
18850
18851#[gpui::test]
18852async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
18853    let (buffer_id, mut cx) = setup_indent_guides_editor(
18854        &"
18855        fn main() {
18856            let a = 1;
18857
18858            let c = 3;
18859        }"
18860        .unindent(),
18861        cx,
18862    )
18863    .await;
18864
18865    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
18866}
18867
18868#[gpui::test]
18869async fn test_indent_guide_complex(cx: &mut TestAppContext) {
18870    let (buffer_id, mut cx) = setup_indent_guides_editor(
18871        &"
18872        fn main() {
18873            let a = 1;
18874
18875            let c = 3;
18876
18877            if a == 3 {
18878                let b = 2;
18879            } else {
18880                let c = 3;
18881            }
18882        }"
18883        .unindent(),
18884        cx,
18885    )
18886    .await;
18887
18888    assert_indent_guides(
18889        0..11,
18890        vec![
18891            indent_guide(buffer_id, 1, 9, 0),
18892            indent_guide(buffer_id, 6, 6, 1),
18893            indent_guide(buffer_id, 8, 8, 1),
18894        ],
18895        None,
18896        &mut cx,
18897    );
18898}
18899
18900#[gpui::test]
18901async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
18902    let (buffer_id, mut cx) = setup_indent_guides_editor(
18903        &"
18904        fn main() {
18905            let a = 1;
18906
18907            let c = 3;
18908
18909            if a == 3 {
18910                let b = 2;
18911            } else {
18912                let c = 3;
18913            }
18914        }"
18915        .unindent(),
18916        cx,
18917    )
18918    .await;
18919
18920    assert_indent_guides(
18921        1..11,
18922        vec![
18923            indent_guide(buffer_id, 1, 9, 0),
18924            indent_guide(buffer_id, 6, 6, 1),
18925            indent_guide(buffer_id, 8, 8, 1),
18926        ],
18927        None,
18928        &mut cx,
18929    );
18930}
18931
18932#[gpui::test]
18933async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
18934    let (buffer_id, mut cx) = setup_indent_guides_editor(
18935        &"
18936        fn main() {
18937            let a = 1;
18938
18939            let c = 3;
18940
18941            if a == 3 {
18942                let b = 2;
18943            } else {
18944                let c = 3;
18945            }
18946        }"
18947        .unindent(),
18948        cx,
18949    )
18950    .await;
18951
18952    assert_indent_guides(
18953        1..10,
18954        vec![
18955            indent_guide(buffer_id, 1, 9, 0),
18956            indent_guide(buffer_id, 6, 6, 1),
18957            indent_guide(buffer_id, 8, 8, 1),
18958        ],
18959        None,
18960        &mut cx,
18961    );
18962}
18963
18964#[gpui::test]
18965async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
18966    let (buffer_id, mut cx) = setup_indent_guides_editor(
18967        &"
18968        fn main() {
18969            if a {
18970                b(
18971                    c,
18972                    d,
18973                )
18974            } else {
18975                e(
18976                    f
18977                )
18978            }
18979        }"
18980        .unindent(),
18981        cx,
18982    )
18983    .await;
18984
18985    assert_indent_guides(
18986        0..11,
18987        vec![
18988            indent_guide(buffer_id, 1, 10, 0),
18989            indent_guide(buffer_id, 2, 5, 1),
18990            indent_guide(buffer_id, 7, 9, 1),
18991            indent_guide(buffer_id, 3, 4, 2),
18992            indent_guide(buffer_id, 8, 8, 2),
18993        ],
18994        None,
18995        &mut cx,
18996    );
18997
18998    cx.update_editor(|editor, window, cx| {
18999        editor.fold_at(MultiBufferRow(2), window, cx);
19000        assert_eq!(
19001            editor.display_text(cx),
19002            "
19003            fn main() {
19004                if a {
19005                    b(⋯
19006                    )
19007                } else {
19008                    e(
19009                        f
19010                    )
19011                }
19012            }"
19013            .unindent()
19014        );
19015    });
19016
19017    assert_indent_guides(
19018        0..11,
19019        vec![
19020            indent_guide(buffer_id, 1, 10, 0),
19021            indent_guide(buffer_id, 2, 5, 1),
19022            indent_guide(buffer_id, 7, 9, 1),
19023            indent_guide(buffer_id, 8, 8, 2),
19024        ],
19025        None,
19026        &mut cx,
19027    );
19028}
19029
19030#[gpui::test]
19031async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
19032    let (buffer_id, mut cx) = setup_indent_guides_editor(
19033        &"
19034        block1
19035            block2
19036                block3
19037                    block4
19038            block2
19039        block1
19040        block1"
19041            .unindent(),
19042        cx,
19043    )
19044    .await;
19045
19046    assert_indent_guides(
19047        1..10,
19048        vec![
19049            indent_guide(buffer_id, 1, 4, 0),
19050            indent_guide(buffer_id, 2, 3, 1),
19051            indent_guide(buffer_id, 3, 3, 2),
19052        ],
19053        None,
19054        &mut cx,
19055    );
19056}
19057
19058#[gpui::test]
19059async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
19060    let (buffer_id, mut cx) = setup_indent_guides_editor(
19061        &"
19062        block1
19063            block2
19064                block3
19065
19066        block1
19067        block1"
19068            .unindent(),
19069        cx,
19070    )
19071    .await;
19072
19073    assert_indent_guides(
19074        0..6,
19075        vec![
19076            indent_guide(buffer_id, 1, 2, 0),
19077            indent_guide(buffer_id, 2, 2, 1),
19078        ],
19079        None,
19080        &mut cx,
19081    );
19082}
19083
19084#[gpui::test]
19085async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
19086    let (buffer_id, mut cx) = setup_indent_guides_editor(
19087        &"
19088        function component() {
19089        \treturn (
19090        \t\t\t
19091        \t\t<div>
19092        \t\t\t<abc></abc>
19093        \t\t</div>
19094        \t)
19095        }"
19096        .unindent(),
19097        cx,
19098    )
19099    .await;
19100
19101    assert_indent_guides(
19102        0..8,
19103        vec![
19104            indent_guide(buffer_id, 1, 6, 0),
19105            indent_guide(buffer_id, 2, 5, 1),
19106            indent_guide(buffer_id, 4, 4, 2),
19107        ],
19108        None,
19109        &mut cx,
19110    );
19111}
19112
19113#[gpui::test]
19114async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
19115    let (buffer_id, mut cx) = setup_indent_guides_editor(
19116        &"
19117        function component() {
19118        \treturn (
19119        \t
19120        \t\t<div>
19121        \t\t\t<abc></abc>
19122        \t\t</div>
19123        \t)
19124        }"
19125        .unindent(),
19126        cx,
19127    )
19128    .await;
19129
19130    assert_indent_guides(
19131        0..8,
19132        vec![
19133            indent_guide(buffer_id, 1, 6, 0),
19134            indent_guide(buffer_id, 2, 5, 1),
19135            indent_guide(buffer_id, 4, 4, 2),
19136        ],
19137        None,
19138        &mut cx,
19139    );
19140}
19141
19142#[gpui::test]
19143async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
19144    let (buffer_id, mut cx) = setup_indent_guides_editor(
19145        &"
19146        block1
19147
19148
19149
19150            block2
19151        "
19152        .unindent(),
19153        cx,
19154    )
19155    .await;
19156
19157    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
19158}
19159
19160#[gpui::test]
19161async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
19162    let (buffer_id, mut cx) = setup_indent_guides_editor(
19163        &"
19164        def a:
19165        \tb = 3
19166        \tif True:
19167        \t\tc = 4
19168        \t\td = 5
19169        \tprint(b)
19170        "
19171        .unindent(),
19172        cx,
19173    )
19174    .await;
19175
19176    assert_indent_guides(
19177        0..6,
19178        vec![
19179            indent_guide(buffer_id, 1, 5, 0),
19180            indent_guide(buffer_id, 3, 4, 1),
19181        ],
19182        None,
19183        &mut cx,
19184    );
19185}
19186
19187#[gpui::test]
19188async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
19189    let (buffer_id, mut cx) = setup_indent_guides_editor(
19190        &"
19191    fn main() {
19192        let a = 1;
19193    }"
19194        .unindent(),
19195        cx,
19196    )
19197    .await;
19198
19199    cx.update_editor(|editor, window, cx| {
19200        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19201            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
19202        });
19203    });
19204
19205    assert_indent_guides(
19206        0..3,
19207        vec![indent_guide(buffer_id, 1, 1, 0)],
19208        Some(vec![0]),
19209        &mut cx,
19210    );
19211}
19212
19213#[gpui::test]
19214async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
19215    let (buffer_id, mut cx) = setup_indent_guides_editor(
19216        &"
19217    fn main() {
19218        if 1 == 2 {
19219            let a = 1;
19220        }
19221    }"
19222        .unindent(),
19223        cx,
19224    )
19225    .await;
19226
19227    cx.update_editor(|editor, window, cx| {
19228        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19229            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
19230        });
19231    });
19232
19233    assert_indent_guides(
19234        0..4,
19235        vec![
19236            indent_guide(buffer_id, 1, 3, 0),
19237            indent_guide(buffer_id, 2, 2, 1),
19238        ],
19239        Some(vec![1]),
19240        &mut cx,
19241    );
19242
19243    cx.update_editor(|editor, window, cx| {
19244        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19245            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
19246        });
19247    });
19248
19249    assert_indent_guides(
19250        0..4,
19251        vec![
19252            indent_guide(buffer_id, 1, 3, 0),
19253            indent_guide(buffer_id, 2, 2, 1),
19254        ],
19255        Some(vec![1]),
19256        &mut cx,
19257    );
19258
19259    cx.update_editor(|editor, window, cx| {
19260        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19261            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
19262        });
19263    });
19264
19265    assert_indent_guides(
19266        0..4,
19267        vec![
19268            indent_guide(buffer_id, 1, 3, 0),
19269            indent_guide(buffer_id, 2, 2, 1),
19270        ],
19271        Some(vec![0]),
19272        &mut cx,
19273    );
19274}
19275
19276#[gpui::test]
19277async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
19278    let (buffer_id, mut cx) = setup_indent_guides_editor(
19279        &"
19280    fn main() {
19281        let a = 1;
19282
19283        let b = 2;
19284    }"
19285        .unindent(),
19286        cx,
19287    )
19288    .await;
19289
19290    cx.update_editor(|editor, window, cx| {
19291        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19292            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
19293        });
19294    });
19295
19296    assert_indent_guides(
19297        0..5,
19298        vec![indent_guide(buffer_id, 1, 3, 0)],
19299        Some(vec![0]),
19300        &mut cx,
19301    );
19302}
19303
19304#[gpui::test]
19305async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
19306    let (buffer_id, mut cx) = setup_indent_guides_editor(
19307        &"
19308    def m:
19309        a = 1
19310        pass"
19311            .unindent(),
19312        cx,
19313    )
19314    .await;
19315
19316    cx.update_editor(|editor, window, cx| {
19317        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19318            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
19319        });
19320    });
19321
19322    assert_indent_guides(
19323        0..3,
19324        vec![indent_guide(buffer_id, 1, 2, 0)],
19325        Some(vec![0]),
19326        &mut cx,
19327    );
19328}
19329
19330#[gpui::test]
19331async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
19332    init_test(cx, |_| {});
19333    let mut cx = EditorTestContext::new(cx).await;
19334    let text = indoc! {
19335        "
19336        impl A {
19337            fn b() {
19338                0;
19339                3;
19340                5;
19341                6;
19342                7;
19343            }
19344        }
19345        "
19346    };
19347    let base_text = indoc! {
19348        "
19349        impl A {
19350            fn b() {
19351                0;
19352                1;
19353                2;
19354                3;
19355                4;
19356            }
19357            fn c() {
19358                5;
19359                6;
19360                7;
19361            }
19362        }
19363        "
19364    };
19365
19366    cx.update_editor(|editor, window, cx| {
19367        editor.set_text(text, window, cx);
19368
19369        editor.buffer().update(cx, |multibuffer, cx| {
19370            let buffer = multibuffer.as_singleton().unwrap();
19371            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
19372
19373            multibuffer.set_all_diff_hunks_expanded(cx);
19374            multibuffer.add_diff(diff, cx);
19375
19376            buffer.read(cx).remote_id()
19377        })
19378    });
19379    cx.run_until_parked();
19380
19381    cx.assert_state_with_diff(
19382        indoc! { "
19383          impl A {
19384              fn b() {
19385                  0;
19386        -         1;
19387        -         2;
19388                  3;
19389        -         4;
19390        -     }
19391        -     fn c() {
19392                  5;
19393                  6;
19394                  7;
19395              }
19396          }
19397          ˇ"
19398        }
19399        .to_string(),
19400    );
19401
19402    let mut actual_guides = cx.update_editor(|editor, window, cx| {
19403        editor
19404            .snapshot(window, cx)
19405            .buffer_snapshot
19406            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
19407            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
19408            .collect::<Vec<_>>()
19409    });
19410    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
19411    assert_eq!(
19412        actual_guides,
19413        vec![
19414            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
19415            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
19416            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
19417        ]
19418    );
19419}
19420
19421#[gpui::test]
19422async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19423    init_test(cx, |_| {});
19424    let mut cx = EditorTestContext::new(cx).await;
19425
19426    let diff_base = r#"
19427        a
19428        b
19429        c
19430        "#
19431    .unindent();
19432
19433    cx.set_state(
19434        &r#"
19435        ˇA
19436        b
19437        C
19438        "#
19439        .unindent(),
19440    );
19441    cx.set_head_text(&diff_base);
19442    cx.update_editor(|editor, window, cx| {
19443        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19444    });
19445    executor.run_until_parked();
19446
19447    let both_hunks_expanded = r#"
19448        - a
19449        + ˇA
19450          b
19451        - c
19452        + C
19453        "#
19454    .unindent();
19455
19456    cx.assert_state_with_diff(both_hunks_expanded.clone());
19457
19458    let hunk_ranges = cx.update_editor(|editor, window, cx| {
19459        let snapshot = editor.snapshot(window, cx);
19460        let hunks = editor
19461            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19462            .collect::<Vec<_>>();
19463        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
19464        let buffer_id = hunks[0].buffer_id;
19465        hunks
19466            .into_iter()
19467            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
19468            .collect::<Vec<_>>()
19469    });
19470    assert_eq!(hunk_ranges.len(), 2);
19471
19472    cx.update_editor(|editor, _, cx| {
19473        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19474    });
19475    executor.run_until_parked();
19476
19477    let second_hunk_expanded = r#"
19478          ˇA
19479          b
19480        - c
19481        + C
19482        "#
19483    .unindent();
19484
19485    cx.assert_state_with_diff(second_hunk_expanded);
19486
19487    cx.update_editor(|editor, _, cx| {
19488        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19489    });
19490    executor.run_until_parked();
19491
19492    cx.assert_state_with_diff(both_hunks_expanded.clone());
19493
19494    cx.update_editor(|editor, _, cx| {
19495        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
19496    });
19497    executor.run_until_parked();
19498
19499    let first_hunk_expanded = r#"
19500        - a
19501        + ˇA
19502          b
19503          C
19504        "#
19505    .unindent();
19506
19507    cx.assert_state_with_diff(first_hunk_expanded);
19508
19509    cx.update_editor(|editor, _, cx| {
19510        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
19511    });
19512    executor.run_until_parked();
19513
19514    cx.assert_state_with_diff(both_hunks_expanded);
19515
19516    cx.set_state(
19517        &r#"
19518        ˇA
19519        b
19520        "#
19521        .unindent(),
19522    );
19523    cx.run_until_parked();
19524
19525    // TODO this cursor position seems bad
19526    cx.assert_state_with_diff(
19527        r#"
19528        - ˇa
19529        + A
19530          b
19531        "#
19532        .unindent(),
19533    );
19534
19535    cx.update_editor(|editor, window, cx| {
19536        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19537    });
19538
19539    cx.assert_state_with_diff(
19540        r#"
19541            - ˇa
19542            + A
19543              b
19544            - c
19545            "#
19546        .unindent(),
19547    );
19548
19549    let hunk_ranges = cx.update_editor(|editor, window, cx| {
19550        let snapshot = editor.snapshot(window, cx);
19551        let hunks = editor
19552            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19553            .collect::<Vec<_>>();
19554        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
19555        let buffer_id = hunks[0].buffer_id;
19556        hunks
19557            .into_iter()
19558            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
19559            .collect::<Vec<_>>()
19560    });
19561    assert_eq!(hunk_ranges.len(), 2);
19562
19563    cx.update_editor(|editor, _, cx| {
19564        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
19565    });
19566    executor.run_until_parked();
19567
19568    cx.assert_state_with_diff(
19569        r#"
19570        - ˇa
19571        + A
19572          b
19573        "#
19574        .unindent(),
19575    );
19576}
19577
19578#[gpui::test]
19579async fn test_toggle_deletion_hunk_at_start_of_file(
19580    executor: BackgroundExecutor,
19581    cx: &mut TestAppContext,
19582) {
19583    init_test(cx, |_| {});
19584    let mut cx = EditorTestContext::new(cx).await;
19585
19586    let diff_base = r#"
19587        a
19588        b
19589        c
19590        "#
19591    .unindent();
19592
19593    cx.set_state(
19594        &r#"
19595        ˇb
19596        c
19597        "#
19598        .unindent(),
19599    );
19600    cx.set_head_text(&diff_base);
19601    cx.update_editor(|editor, window, cx| {
19602        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19603    });
19604    executor.run_until_parked();
19605
19606    let hunk_expanded = r#"
19607        - a
19608          ˇb
19609          c
19610        "#
19611    .unindent();
19612
19613    cx.assert_state_with_diff(hunk_expanded.clone());
19614
19615    let hunk_ranges = cx.update_editor(|editor, window, cx| {
19616        let snapshot = editor.snapshot(window, cx);
19617        let hunks = editor
19618            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19619            .collect::<Vec<_>>();
19620        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
19621        let buffer_id = hunks[0].buffer_id;
19622        hunks
19623            .into_iter()
19624            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
19625            .collect::<Vec<_>>()
19626    });
19627    assert_eq!(hunk_ranges.len(), 1);
19628
19629    cx.update_editor(|editor, _, cx| {
19630        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19631    });
19632    executor.run_until_parked();
19633
19634    let hunk_collapsed = r#"
19635          ˇb
19636          c
19637        "#
19638    .unindent();
19639
19640    cx.assert_state_with_diff(hunk_collapsed);
19641
19642    cx.update_editor(|editor, _, cx| {
19643        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19644    });
19645    executor.run_until_parked();
19646
19647    cx.assert_state_with_diff(hunk_expanded.clone());
19648}
19649
19650#[gpui::test]
19651async fn test_display_diff_hunks(cx: &mut TestAppContext) {
19652    init_test(cx, |_| {});
19653
19654    let fs = FakeFs::new(cx.executor());
19655    fs.insert_tree(
19656        path!("/test"),
19657        json!({
19658            ".git": {},
19659            "file-1": "ONE\n",
19660            "file-2": "TWO\n",
19661            "file-3": "THREE\n",
19662        }),
19663    )
19664    .await;
19665
19666    fs.set_head_for_repo(
19667        path!("/test/.git").as_ref(),
19668        &[
19669            ("file-1".into(), "one\n".into()),
19670            ("file-2".into(), "two\n".into()),
19671            ("file-3".into(), "three\n".into()),
19672        ],
19673        "deadbeef",
19674    );
19675
19676    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
19677    let mut buffers = vec![];
19678    for i in 1..=3 {
19679        let buffer = project
19680            .update(cx, |project, cx| {
19681                let path = format!(path!("/test/file-{}"), i);
19682                project.open_local_buffer(path, cx)
19683            })
19684            .await
19685            .unwrap();
19686        buffers.push(buffer);
19687    }
19688
19689    let multibuffer = cx.new(|cx| {
19690        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
19691        multibuffer.set_all_diff_hunks_expanded(cx);
19692        for buffer in &buffers {
19693            let snapshot = buffer.read(cx).snapshot();
19694            multibuffer.set_excerpts_for_path(
19695                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
19696                buffer.clone(),
19697                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
19698                DEFAULT_MULTIBUFFER_CONTEXT,
19699                cx,
19700            );
19701        }
19702        multibuffer
19703    });
19704
19705    let editor = cx.add_window(|window, cx| {
19706        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
19707    });
19708    cx.run_until_parked();
19709
19710    let snapshot = editor
19711        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19712        .unwrap();
19713    let hunks = snapshot
19714        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
19715        .map(|hunk| match hunk {
19716            DisplayDiffHunk::Unfolded {
19717                display_row_range, ..
19718            } => display_row_range,
19719            DisplayDiffHunk::Folded { .. } => unreachable!(),
19720        })
19721        .collect::<Vec<_>>();
19722    assert_eq!(
19723        hunks,
19724        [
19725            DisplayRow(2)..DisplayRow(4),
19726            DisplayRow(7)..DisplayRow(9),
19727            DisplayRow(12)..DisplayRow(14),
19728        ]
19729    );
19730}
19731
19732#[gpui::test]
19733async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
19734    init_test(cx, |_| {});
19735
19736    let mut cx = EditorTestContext::new(cx).await;
19737    cx.set_head_text(indoc! { "
19738        one
19739        two
19740        three
19741        four
19742        five
19743        "
19744    });
19745    cx.set_index_text(indoc! { "
19746        one
19747        two
19748        three
19749        four
19750        five
19751        "
19752    });
19753    cx.set_state(indoc! {"
19754        one
19755        TWO
19756        ˇTHREE
19757        FOUR
19758        five
19759    "});
19760    cx.run_until_parked();
19761    cx.update_editor(|editor, window, cx| {
19762        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19763    });
19764    cx.run_until_parked();
19765    cx.assert_index_text(Some(indoc! {"
19766        one
19767        TWO
19768        THREE
19769        FOUR
19770        five
19771    "}));
19772    cx.set_state(indoc! { "
19773        one
19774        TWO
19775        ˇTHREE-HUNDRED
19776        FOUR
19777        five
19778    "});
19779    cx.run_until_parked();
19780    cx.update_editor(|editor, window, cx| {
19781        let snapshot = editor.snapshot(window, cx);
19782        let hunks = editor
19783            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19784            .collect::<Vec<_>>();
19785        assert_eq!(hunks.len(), 1);
19786        assert_eq!(
19787            hunks[0].status(),
19788            DiffHunkStatus {
19789                kind: DiffHunkStatusKind::Modified,
19790                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
19791            }
19792        );
19793
19794        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19795    });
19796    cx.run_until_parked();
19797    cx.assert_index_text(Some(indoc! {"
19798        one
19799        TWO
19800        THREE-HUNDRED
19801        FOUR
19802        five
19803    "}));
19804}
19805
19806#[gpui::test]
19807fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
19808    init_test(cx, |_| {});
19809
19810    let editor = cx.add_window(|window, cx| {
19811        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
19812        build_editor(buffer, window, cx)
19813    });
19814
19815    let render_args = Arc::new(Mutex::new(None));
19816    let snapshot = editor
19817        .update(cx, |editor, window, cx| {
19818            let snapshot = editor.buffer().read(cx).snapshot(cx);
19819            let range =
19820                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
19821
19822            struct RenderArgs {
19823                row: MultiBufferRow,
19824                folded: bool,
19825                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
19826            }
19827
19828            let crease = Crease::inline(
19829                range,
19830                FoldPlaceholder::test(),
19831                {
19832                    let toggle_callback = render_args.clone();
19833                    move |row, folded, callback, _window, _cx| {
19834                        *toggle_callback.lock() = Some(RenderArgs {
19835                            row,
19836                            folded,
19837                            callback,
19838                        });
19839                        div()
19840                    }
19841                },
19842                |_row, _folded, _window, _cx| div(),
19843            );
19844
19845            editor.insert_creases(Some(crease), cx);
19846            let snapshot = editor.snapshot(window, cx);
19847            let _div =
19848                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
19849            snapshot
19850        })
19851        .unwrap();
19852
19853    let render_args = render_args.lock().take().unwrap();
19854    assert_eq!(render_args.row, MultiBufferRow(1));
19855    assert!(!render_args.folded);
19856    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
19857
19858    cx.update_window(*editor, |_, window, cx| {
19859        (render_args.callback)(true, window, cx)
19860    })
19861    .unwrap();
19862    let snapshot = editor
19863        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19864        .unwrap();
19865    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
19866
19867    cx.update_window(*editor, |_, window, cx| {
19868        (render_args.callback)(false, window, cx)
19869    })
19870    .unwrap();
19871    let snapshot = editor
19872        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19873        .unwrap();
19874    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
19875}
19876
19877#[gpui::test]
19878async fn test_input_text(cx: &mut TestAppContext) {
19879    init_test(cx, |_| {});
19880    let mut cx = EditorTestContext::new(cx).await;
19881
19882    cx.set_state(
19883        &r#"ˇone
19884        two
19885
19886        three
19887        fourˇ
19888        five
19889
19890        siˇx"#
19891            .unindent(),
19892    );
19893
19894    cx.dispatch_action(HandleInput(String::new()));
19895    cx.assert_editor_state(
19896        &r#"ˇone
19897        two
19898
19899        three
19900        fourˇ
19901        five
19902
19903        siˇx"#
19904            .unindent(),
19905    );
19906
19907    cx.dispatch_action(HandleInput("AAAA".to_string()));
19908    cx.assert_editor_state(
19909        &r#"AAAAˇone
19910        two
19911
19912        three
19913        fourAAAAˇ
19914        five
19915
19916        siAAAAˇx"#
19917            .unindent(),
19918    );
19919}
19920
19921#[gpui::test]
19922async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
19923    init_test(cx, |_| {});
19924
19925    let mut cx = EditorTestContext::new(cx).await;
19926    cx.set_state(
19927        r#"let foo = 1;
19928let foo = 2;
19929let foo = 3;
19930let fooˇ = 4;
19931let foo = 5;
19932let foo = 6;
19933let foo = 7;
19934let foo = 8;
19935let foo = 9;
19936let foo = 10;
19937let foo = 11;
19938let foo = 12;
19939let foo = 13;
19940let foo = 14;
19941let foo = 15;"#,
19942    );
19943
19944    cx.update_editor(|e, window, cx| {
19945        assert_eq!(
19946            e.next_scroll_position,
19947            NextScrollCursorCenterTopBottom::Center,
19948            "Default next scroll direction is center",
19949        );
19950
19951        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19952        assert_eq!(
19953            e.next_scroll_position,
19954            NextScrollCursorCenterTopBottom::Top,
19955            "After center, next scroll direction should be top",
19956        );
19957
19958        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19959        assert_eq!(
19960            e.next_scroll_position,
19961            NextScrollCursorCenterTopBottom::Bottom,
19962            "After top, next scroll direction should be bottom",
19963        );
19964
19965        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19966        assert_eq!(
19967            e.next_scroll_position,
19968            NextScrollCursorCenterTopBottom::Center,
19969            "After bottom, scrolling should start over",
19970        );
19971
19972        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19973        assert_eq!(
19974            e.next_scroll_position,
19975            NextScrollCursorCenterTopBottom::Top,
19976            "Scrolling continues if retriggered fast enough"
19977        );
19978    });
19979
19980    cx.executor()
19981        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
19982    cx.executor().run_until_parked();
19983    cx.update_editor(|e, _, _| {
19984        assert_eq!(
19985            e.next_scroll_position,
19986            NextScrollCursorCenterTopBottom::Center,
19987            "If scrolling is not triggered fast enough, it should reset"
19988        );
19989    });
19990}
19991
19992#[gpui::test]
19993async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
19994    init_test(cx, |_| {});
19995    let mut cx = EditorLspTestContext::new_rust(
19996        lsp::ServerCapabilities {
19997            definition_provider: Some(lsp::OneOf::Left(true)),
19998            references_provider: Some(lsp::OneOf::Left(true)),
19999            ..lsp::ServerCapabilities::default()
20000        },
20001        cx,
20002    )
20003    .await;
20004
20005    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
20006        let go_to_definition = cx
20007            .lsp
20008            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
20009                move |params, _| async move {
20010                    if empty_go_to_definition {
20011                        Ok(None)
20012                    } else {
20013                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
20014                            uri: params.text_document_position_params.text_document.uri,
20015                            range: lsp::Range::new(
20016                                lsp::Position::new(4, 3),
20017                                lsp::Position::new(4, 6),
20018                            ),
20019                        })))
20020                    }
20021                },
20022            );
20023        let references = cx
20024            .lsp
20025            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
20026                Ok(Some(vec![lsp::Location {
20027                    uri: params.text_document_position.text_document.uri,
20028                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
20029                }]))
20030            });
20031        (go_to_definition, references)
20032    };
20033
20034    cx.set_state(
20035        &r#"fn one() {
20036            let mut a = ˇtwo();
20037        }
20038
20039        fn two() {}"#
20040            .unindent(),
20041    );
20042    set_up_lsp_handlers(false, &mut cx);
20043    let navigated = cx
20044        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
20045        .await
20046        .expect("Failed to navigate to definition");
20047    assert_eq!(
20048        navigated,
20049        Navigated::Yes,
20050        "Should have navigated to definition from the GetDefinition response"
20051    );
20052    cx.assert_editor_state(
20053        &r#"fn one() {
20054            let mut a = two();
20055        }
20056
20057        fn «twoˇ»() {}"#
20058            .unindent(),
20059    );
20060
20061    let editors = cx.update_workspace(|workspace, _, cx| {
20062        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
20063    });
20064    cx.update_editor(|_, _, test_editor_cx| {
20065        assert_eq!(
20066            editors.len(),
20067            1,
20068            "Initially, only one, test, editor should be open in the workspace"
20069        );
20070        assert_eq!(
20071            test_editor_cx.entity(),
20072            editors.last().expect("Asserted len is 1").clone()
20073        );
20074    });
20075
20076    set_up_lsp_handlers(true, &mut cx);
20077    let navigated = cx
20078        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
20079        .await
20080        .expect("Failed to navigate to lookup references");
20081    assert_eq!(
20082        navigated,
20083        Navigated::Yes,
20084        "Should have navigated to references as a fallback after empty GoToDefinition response"
20085    );
20086    // We should not change the selections in the existing file,
20087    // if opening another milti buffer with the references
20088    cx.assert_editor_state(
20089        &r#"fn one() {
20090            let mut a = two();
20091        }
20092
20093        fn «twoˇ»() {}"#
20094            .unindent(),
20095    );
20096    let editors = cx.update_workspace(|workspace, _, cx| {
20097        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
20098    });
20099    cx.update_editor(|_, _, test_editor_cx| {
20100        assert_eq!(
20101            editors.len(),
20102            2,
20103            "After falling back to references search, we open a new editor with the results"
20104        );
20105        let references_fallback_text = editors
20106            .into_iter()
20107            .find(|new_editor| *new_editor != test_editor_cx.entity())
20108            .expect("Should have one non-test editor now")
20109            .read(test_editor_cx)
20110            .text(test_editor_cx);
20111        assert_eq!(
20112            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
20113            "Should use the range from the references response and not the GoToDefinition one"
20114        );
20115    });
20116}
20117
20118#[gpui::test]
20119async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
20120    init_test(cx, |_| {});
20121    cx.update(|cx| {
20122        let mut editor_settings = EditorSettings::get_global(cx).clone();
20123        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
20124        EditorSettings::override_global(editor_settings, cx);
20125    });
20126    let mut cx = EditorLspTestContext::new_rust(
20127        lsp::ServerCapabilities {
20128            definition_provider: Some(lsp::OneOf::Left(true)),
20129            references_provider: Some(lsp::OneOf::Left(true)),
20130            ..lsp::ServerCapabilities::default()
20131        },
20132        cx,
20133    )
20134    .await;
20135    let original_state = r#"fn one() {
20136        let mut a = ˇtwo();
20137    }
20138
20139    fn two() {}"#
20140        .unindent();
20141    cx.set_state(&original_state);
20142
20143    let mut go_to_definition = cx
20144        .lsp
20145        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
20146            move |_, _| async move { Ok(None) },
20147        );
20148    let _references = cx
20149        .lsp
20150        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
20151            panic!("Should not call for references with no go to definition fallback")
20152        });
20153
20154    let navigated = cx
20155        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
20156        .await
20157        .expect("Failed to navigate to lookup references");
20158    go_to_definition
20159        .next()
20160        .await
20161        .expect("Should have called the go_to_definition handler");
20162
20163    assert_eq!(
20164        navigated,
20165        Navigated::No,
20166        "Should have navigated to references as a fallback after empty GoToDefinition response"
20167    );
20168    cx.assert_editor_state(&original_state);
20169    let editors = cx.update_workspace(|workspace, _, cx| {
20170        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
20171    });
20172    cx.update_editor(|_, _, _| {
20173        assert_eq!(
20174            editors.len(),
20175            1,
20176            "After unsuccessful fallback, no other editor should have been opened"
20177        );
20178    });
20179}
20180
20181#[gpui::test]
20182async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
20183    init_test(cx, |_| {});
20184
20185    let language = Arc::new(Language::new(
20186        LanguageConfig::default(),
20187        Some(tree_sitter_rust::LANGUAGE.into()),
20188    ));
20189
20190    let text = r#"
20191        #[cfg(test)]
20192        mod tests() {
20193            #[test]
20194            fn runnable_1() {
20195                let a = 1;
20196            }
20197
20198            #[test]
20199            fn runnable_2() {
20200                let a = 1;
20201                let b = 2;
20202            }
20203        }
20204    "#
20205    .unindent();
20206
20207    let fs = FakeFs::new(cx.executor());
20208    fs.insert_file("/file.rs", Default::default()).await;
20209
20210    let project = Project::test(fs, ["/a".as_ref()], cx).await;
20211    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20212    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20213    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
20214    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
20215
20216    let editor = cx.new_window_entity(|window, cx| {
20217        Editor::new(
20218            EditorMode::full(),
20219            multi_buffer,
20220            Some(project.clone()),
20221            window,
20222            cx,
20223        )
20224    });
20225
20226    editor.update_in(cx, |editor, window, cx| {
20227        let snapshot = editor.buffer().read(cx).snapshot(cx);
20228        editor.tasks.insert(
20229            (buffer.read(cx).remote_id(), 3),
20230            RunnableTasks {
20231                templates: vec![],
20232                offset: snapshot.anchor_before(43),
20233                column: 0,
20234                extra_variables: HashMap::default(),
20235                context_range: BufferOffset(43)..BufferOffset(85),
20236            },
20237        );
20238        editor.tasks.insert(
20239            (buffer.read(cx).remote_id(), 8),
20240            RunnableTasks {
20241                templates: vec![],
20242                offset: snapshot.anchor_before(86),
20243                column: 0,
20244                extra_variables: HashMap::default(),
20245                context_range: BufferOffset(86)..BufferOffset(191),
20246            },
20247        );
20248
20249        // Test finding task when cursor is inside function body
20250        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20251            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
20252        });
20253        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
20254        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
20255
20256        // Test finding task when cursor is on function name
20257        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20258            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
20259        });
20260        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
20261        assert_eq!(row, 8, "Should find task when cursor is on function name");
20262    });
20263}
20264
20265#[gpui::test]
20266async fn test_folding_buffers(cx: &mut TestAppContext) {
20267    init_test(cx, |_| {});
20268
20269    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
20270    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
20271    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
20272
20273    let fs = FakeFs::new(cx.executor());
20274    fs.insert_tree(
20275        path!("/a"),
20276        json!({
20277            "first.rs": sample_text_1,
20278            "second.rs": sample_text_2,
20279            "third.rs": sample_text_3,
20280        }),
20281    )
20282    .await;
20283    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20284    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20285    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20286    let worktree = project.update(cx, |project, cx| {
20287        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
20288        assert_eq!(worktrees.len(), 1);
20289        worktrees.pop().unwrap()
20290    });
20291    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
20292
20293    let buffer_1 = project
20294        .update(cx, |project, cx| {
20295            project.open_buffer((worktree_id, "first.rs"), cx)
20296        })
20297        .await
20298        .unwrap();
20299    let buffer_2 = project
20300        .update(cx, |project, cx| {
20301            project.open_buffer((worktree_id, "second.rs"), cx)
20302        })
20303        .await
20304        .unwrap();
20305    let buffer_3 = project
20306        .update(cx, |project, cx| {
20307            project.open_buffer((worktree_id, "third.rs"), cx)
20308        })
20309        .await
20310        .unwrap();
20311
20312    let multi_buffer = cx.new(|cx| {
20313        let mut multi_buffer = MultiBuffer::new(ReadWrite);
20314        multi_buffer.push_excerpts(
20315            buffer_1.clone(),
20316            [
20317                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20318                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20319                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20320            ],
20321            cx,
20322        );
20323        multi_buffer.push_excerpts(
20324            buffer_2.clone(),
20325            [
20326                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20327                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20328                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20329            ],
20330            cx,
20331        );
20332        multi_buffer.push_excerpts(
20333            buffer_3.clone(),
20334            [
20335                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20336                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20337                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20338            ],
20339            cx,
20340        );
20341        multi_buffer
20342    });
20343    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20344        Editor::new(
20345            EditorMode::full(),
20346            multi_buffer.clone(),
20347            Some(project.clone()),
20348            window,
20349            cx,
20350        )
20351    });
20352
20353    assert_eq!(
20354        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20355        "\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",
20356    );
20357
20358    multi_buffer_editor.update(cx, |editor, cx| {
20359        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
20360    });
20361    assert_eq!(
20362        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20363        "\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",
20364        "After folding the first buffer, its text should not be displayed"
20365    );
20366
20367    multi_buffer_editor.update(cx, |editor, cx| {
20368        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
20369    });
20370    assert_eq!(
20371        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20372        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
20373        "After folding the second buffer, its text should not be displayed"
20374    );
20375
20376    multi_buffer_editor.update(cx, |editor, cx| {
20377        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
20378    });
20379    assert_eq!(
20380        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20381        "\n\n\n\n\n",
20382        "After folding the third buffer, its text should not be displayed"
20383    );
20384
20385    // Emulate selection inside the fold logic, that should work
20386    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20387        editor
20388            .snapshot(window, cx)
20389            .next_line_boundary(Point::new(0, 4));
20390    });
20391
20392    multi_buffer_editor.update(cx, |editor, cx| {
20393        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
20394    });
20395    assert_eq!(
20396        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20397        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
20398        "After unfolding the second buffer, its text should be displayed"
20399    );
20400
20401    // Typing inside of buffer 1 causes that buffer to be unfolded.
20402    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20403        assert_eq!(
20404            multi_buffer
20405                .read(cx)
20406                .snapshot(cx)
20407                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
20408                .collect::<String>(),
20409            "bbbb"
20410        );
20411        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
20412            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
20413        });
20414        editor.handle_input("B", window, cx);
20415    });
20416
20417    assert_eq!(
20418        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20419        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
20420        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
20421    );
20422
20423    multi_buffer_editor.update(cx, |editor, cx| {
20424        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
20425    });
20426    assert_eq!(
20427        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20428        "\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",
20429        "After unfolding the all buffers, all original text should be displayed"
20430    );
20431}
20432
20433#[gpui::test]
20434async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
20435    init_test(cx, |_| {});
20436
20437    let sample_text_1 = "1111\n2222\n3333".to_string();
20438    let sample_text_2 = "4444\n5555\n6666".to_string();
20439    let sample_text_3 = "7777\n8888\n9999".to_string();
20440
20441    let fs = FakeFs::new(cx.executor());
20442    fs.insert_tree(
20443        path!("/a"),
20444        json!({
20445            "first.rs": sample_text_1,
20446            "second.rs": sample_text_2,
20447            "third.rs": sample_text_3,
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    let worktree = project.update(cx, |project, cx| {
20455        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
20456        assert_eq!(worktrees.len(), 1);
20457        worktrees.pop().unwrap()
20458    });
20459    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
20460
20461    let buffer_1 = project
20462        .update(cx, |project, cx| {
20463            project.open_buffer((worktree_id, "first.rs"), cx)
20464        })
20465        .await
20466        .unwrap();
20467    let buffer_2 = project
20468        .update(cx, |project, cx| {
20469            project.open_buffer((worktree_id, "second.rs"), cx)
20470        })
20471        .await
20472        .unwrap();
20473    let buffer_3 = project
20474        .update(cx, |project, cx| {
20475            project.open_buffer((worktree_id, "third.rs"), cx)
20476        })
20477        .await
20478        .unwrap();
20479
20480    let multi_buffer = cx.new(|cx| {
20481        let mut multi_buffer = MultiBuffer::new(ReadWrite);
20482        multi_buffer.push_excerpts(
20483            buffer_1.clone(),
20484            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
20485            cx,
20486        );
20487        multi_buffer.push_excerpts(
20488            buffer_2.clone(),
20489            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
20490            cx,
20491        );
20492        multi_buffer.push_excerpts(
20493            buffer_3.clone(),
20494            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
20495            cx,
20496        );
20497        multi_buffer
20498    });
20499
20500    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20501        Editor::new(
20502            EditorMode::full(),
20503            multi_buffer,
20504            Some(project.clone()),
20505            window,
20506            cx,
20507        )
20508    });
20509
20510    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
20511    assert_eq!(
20512        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20513        full_text,
20514    );
20515
20516    multi_buffer_editor.update(cx, |editor, cx| {
20517        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
20518    });
20519    assert_eq!(
20520        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20521        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
20522        "After folding the first buffer, its text should not be displayed"
20523    );
20524
20525    multi_buffer_editor.update(cx, |editor, cx| {
20526        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
20527    });
20528
20529    assert_eq!(
20530        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20531        "\n\n\n\n\n\n7777\n8888\n9999",
20532        "After folding the second buffer, its text should not be displayed"
20533    );
20534
20535    multi_buffer_editor.update(cx, |editor, cx| {
20536        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
20537    });
20538    assert_eq!(
20539        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20540        "\n\n\n\n\n",
20541        "After folding the third buffer, its text should not be displayed"
20542    );
20543
20544    multi_buffer_editor.update(cx, |editor, cx| {
20545        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
20546    });
20547    assert_eq!(
20548        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20549        "\n\n\n\n4444\n5555\n6666\n\n",
20550        "After unfolding the second buffer, its text should be displayed"
20551    );
20552
20553    multi_buffer_editor.update(cx, |editor, cx| {
20554        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
20555    });
20556    assert_eq!(
20557        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20558        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
20559        "After unfolding the first buffer, its text should be displayed"
20560    );
20561
20562    multi_buffer_editor.update(cx, |editor, cx| {
20563        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
20564    });
20565    assert_eq!(
20566        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20567        full_text,
20568        "After unfolding all buffers, all original text should be displayed"
20569    );
20570}
20571
20572#[gpui::test]
20573async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
20574    init_test(cx, |_| {});
20575
20576    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
20577
20578    let fs = FakeFs::new(cx.executor());
20579    fs.insert_tree(
20580        path!("/a"),
20581        json!({
20582            "main.rs": sample_text,
20583        }),
20584    )
20585    .await;
20586    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20587    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20588    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20589    let worktree = project.update(cx, |project, cx| {
20590        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
20591        assert_eq!(worktrees.len(), 1);
20592        worktrees.pop().unwrap()
20593    });
20594    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
20595
20596    let buffer_1 = project
20597        .update(cx, |project, cx| {
20598            project.open_buffer((worktree_id, "main.rs"), cx)
20599        })
20600        .await
20601        .unwrap();
20602
20603    let multi_buffer = cx.new(|cx| {
20604        let mut multi_buffer = MultiBuffer::new(ReadWrite);
20605        multi_buffer.push_excerpts(
20606            buffer_1.clone(),
20607            [ExcerptRange::new(
20608                Point::new(0, 0)
20609                    ..Point::new(
20610                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
20611                        0,
20612                    ),
20613            )],
20614            cx,
20615        );
20616        multi_buffer
20617    });
20618    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20619        Editor::new(
20620            EditorMode::full(),
20621            multi_buffer,
20622            Some(project.clone()),
20623            window,
20624            cx,
20625        )
20626    });
20627
20628    let selection_range = Point::new(1, 0)..Point::new(2, 0);
20629    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20630        enum TestHighlight {}
20631        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
20632        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
20633        editor.highlight_text::<TestHighlight>(
20634            vec![highlight_range.clone()],
20635            HighlightStyle::color(Hsla::green()),
20636            cx,
20637        );
20638        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20639            s.select_ranges(Some(highlight_range))
20640        });
20641    });
20642
20643    let full_text = format!("\n\n{sample_text}");
20644    assert_eq!(
20645        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20646        full_text,
20647    );
20648}
20649
20650#[gpui::test]
20651async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
20652    init_test(cx, |_| {});
20653    cx.update(|cx| {
20654        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
20655            "keymaps/default-linux.json",
20656            cx,
20657        )
20658        .unwrap();
20659        cx.bind_keys(default_key_bindings);
20660    });
20661
20662    let (editor, cx) = cx.add_window_view(|window, cx| {
20663        let multi_buffer = MultiBuffer::build_multi(
20664            [
20665                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
20666                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
20667                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
20668                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
20669            ],
20670            cx,
20671        );
20672        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
20673
20674        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
20675        // fold all but the second buffer, so that we test navigating between two
20676        // adjacent folded buffers, as well as folded buffers at the start and
20677        // end the multibuffer
20678        editor.fold_buffer(buffer_ids[0], cx);
20679        editor.fold_buffer(buffer_ids[2], cx);
20680        editor.fold_buffer(buffer_ids[3], cx);
20681
20682        editor
20683    });
20684    cx.simulate_resize(size(px(1000.), px(1000.)));
20685
20686    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
20687    cx.assert_excerpts_with_selections(indoc! {"
20688        [EXCERPT]
20689        ˇ[FOLDED]
20690        [EXCERPT]
20691        a1
20692        b1
20693        [EXCERPT]
20694        [FOLDED]
20695        [EXCERPT]
20696        [FOLDED]
20697        "
20698    });
20699    cx.simulate_keystroke("down");
20700    cx.assert_excerpts_with_selections(indoc! {"
20701        [EXCERPT]
20702        [FOLDED]
20703        [EXCERPT]
20704        ˇa1
20705        b1
20706        [EXCERPT]
20707        [FOLDED]
20708        [EXCERPT]
20709        [FOLDED]
20710        "
20711    });
20712    cx.simulate_keystroke("down");
20713    cx.assert_excerpts_with_selections(indoc! {"
20714        [EXCERPT]
20715        [FOLDED]
20716        [EXCERPT]
20717        a1
20718        ˇb1
20719        [EXCERPT]
20720        [FOLDED]
20721        [EXCERPT]
20722        [FOLDED]
20723        "
20724    });
20725    cx.simulate_keystroke("down");
20726    cx.assert_excerpts_with_selections(indoc! {"
20727        [EXCERPT]
20728        [FOLDED]
20729        [EXCERPT]
20730        a1
20731        b1
20732        ˇ[EXCERPT]
20733        [FOLDED]
20734        [EXCERPT]
20735        [FOLDED]
20736        "
20737    });
20738    cx.simulate_keystroke("down");
20739    cx.assert_excerpts_with_selections(indoc! {"
20740        [EXCERPT]
20741        [FOLDED]
20742        [EXCERPT]
20743        a1
20744        b1
20745        [EXCERPT]
20746        ˇ[FOLDED]
20747        [EXCERPT]
20748        [FOLDED]
20749        "
20750    });
20751    for _ in 0..5 {
20752        cx.simulate_keystroke("down");
20753        cx.assert_excerpts_with_selections(indoc! {"
20754            [EXCERPT]
20755            [FOLDED]
20756            [EXCERPT]
20757            a1
20758            b1
20759            [EXCERPT]
20760            [FOLDED]
20761            [EXCERPT]
20762            ˇ[FOLDED]
20763            "
20764        });
20765    }
20766
20767    cx.simulate_keystroke("up");
20768    cx.assert_excerpts_with_selections(indoc! {"
20769        [EXCERPT]
20770        [FOLDED]
20771        [EXCERPT]
20772        a1
20773        b1
20774        [EXCERPT]
20775        ˇ[FOLDED]
20776        [EXCERPT]
20777        [FOLDED]
20778        "
20779    });
20780    cx.simulate_keystroke("up");
20781    cx.assert_excerpts_with_selections(indoc! {"
20782        [EXCERPT]
20783        [FOLDED]
20784        [EXCERPT]
20785        a1
20786        b1
20787        ˇ[EXCERPT]
20788        [FOLDED]
20789        [EXCERPT]
20790        [FOLDED]
20791        "
20792    });
20793    cx.simulate_keystroke("up");
20794    cx.assert_excerpts_with_selections(indoc! {"
20795        [EXCERPT]
20796        [FOLDED]
20797        [EXCERPT]
20798        a1
20799        ˇb1
20800        [EXCERPT]
20801        [FOLDED]
20802        [EXCERPT]
20803        [FOLDED]
20804        "
20805    });
20806    cx.simulate_keystroke("up");
20807    cx.assert_excerpts_with_selections(indoc! {"
20808        [EXCERPT]
20809        [FOLDED]
20810        [EXCERPT]
20811        ˇa1
20812        b1
20813        [EXCERPT]
20814        [FOLDED]
20815        [EXCERPT]
20816        [FOLDED]
20817        "
20818    });
20819    for _ in 0..5 {
20820        cx.simulate_keystroke("up");
20821        cx.assert_excerpts_with_selections(indoc! {"
20822            [EXCERPT]
20823            ˇ[FOLDED]
20824            [EXCERPT]
20825            a1
20826            b1
20827            [EXCERPT]
20828            [FOLDED]
20829            [EXCERPT]
20830            [FOLDED]
20831            "
20832        });
20833    }
20834}
20835
20836#[gpui::test]
20837async fn test_edit_prediction_text(cx: &mut TestAppContext) {
20838    init_test(cx, |_| {});
20839
20840    // Simple insertion
20841    assert_highlighted_edits(
20842        "Hello, world!",
20843        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
20844        true,
20845        cx,
20846        |highlighted_edits, cx| {
20847            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
20848            assert_eq!(highlighted_edits.highlights.len(), 1);
20849            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
20850            assert_eq!(
20851                highlighted_edits.highlights[0].1.background_color,
20852                Some(cx.theme().status().created_background)
20853            );
20854        },
20855    )
20856    .await;
20857
20858    // Replacement
20859    assert_highlighted_edits(
20860        "This is a test.",
20861        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
20862        false,
20863        cx,
20864        |highlighted_edits, cx| {
20865            assert_eq!(highlighted_edits.text, "That is a test.");
20866            assert_eq!(highlighted_edits.highlights.len(), 1);
20867            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
20868            assert_eq!(
20869                highlighted_edits.highlights[0].1.background_color,
20870                Some(cx.theme().status().created_background)
20871            );
20872        },
20873    )
20874    .await;
20875
20876    // Multiple edits
20877    assert_highlighted_edits(
20878        "Hello, world!",
20879        vec![
20880            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
20881            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
20882        ],
20883        false,
20884        cx,
20885        |highlighted_edits, cx| {
20886            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
20887            assert_eq!(highlighted_edits.highlights.len(), 2);
20888            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
20889            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
20890            assert_eq!(
20891                highlighted_edits.highlights[0].1.background_color,
20892                Some(cx.theme().status().created_background)
20893            );
20894            assert_eq!(
20895                highlighted_edits.highlights[1].1.background_color,
20896                Some(cx.theme().status().created_background)
20897            );
20898        },
20899    )
20900    .await;
20901
20902    // Multiple lines with edits
20903    assert_highlighted_edits(
20904        "First line\nSecond line\nThird line\nFourth line",
20905        vec![
20906            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
20907            (
20908                Point::new(2, 0)..Point::new(2, 10),
20909                "New third line".to_string(),
20910            ),
20911            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
20912        ],
20913        false,
20914        cx,
20915        |highlighted_edits, cx| {
20916            assert_eq!(
20917                highlighted_edits.text,
20918                "Second modified\nNew third line\nFourth updated line"
20919            );
20920            assert_eq!(highlighted_edits.highlights.len(), 3);
20921            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
20922            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
20923            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
20924            for highlight in &highlighted_edits.highlights {
20925                assert_eq!(
20926                    highlight.1.background_color,
20927                    Some(cx.theme().status().created_background)
20928                );
20929            }
20930        },
20931    )
20932    .await;
20933}
20934
20935#[gpui::test]
20936async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
20937    init_test(cx, |_| {});
20938
20939    // Deletion
20940    assert_highlighted_edits(
20941        "Hello, world!",
20942        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
20943        true,
20944        cx,
20945        |highlighted_edits, cx| {
20946            assert_eq!(highlighted_edits.text, "Hello, world!");
20947            assert_eq!(highlighted_edits.highlights.len(), 1);
20948            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
20949            assert_eq!(
20950                highlighted_edits.highlights[0].1.background_color,
20951                Some(cx.theme().status().deleted_background)
20952            );
20953        },
20954    )
20955    .await;
20956
20957    // Insertion
20958    assert_highlighted_edits(
20959        "Hello, world!",
20960        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
20961        true,
20962        cx,
20963        |highlighted_edits, cx| {
20964            assert_eq!(highlighted_edits.highlights.len(), 1);
20965            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
20966            assert_eq!(
20967                highlighted_edits.highlights[0].1.background_color,
20968                Some(cx.theme().status().created_background)
20969            );
20970        },
20971    )
20972    .await;
20973}
20974
20975async fn assert_highlighted_edits(
20976    text: &str,
20977    edits: Vec<(Range<Point>, String)>,
20978    include_deletions: bool,
20979    cx: &mut TestAppContext,
20980    assertion_fn: impl Fn(HighlightedText, &App),
20981) {
20982    let window = cx.add_window(|window, cx| {
20983        let buffer = MultiBuffer::build_simple(text, cx);
20984        Editor::new(EditorMode::full(), buffer, None, window, cx)
20985    });
20986    let cx = &mut VisualTestContext::from_window(*window, cx);
20987
20988    let (buffer, snapshot) = window
20989        .update(cx, |editor, _window, cx| {
20990            (
20991                editor.buffer().clone(),
20992                editor.buffer().read(cx).snapshot(cx),
20993            )
20994        })
20995        .unwrap();
20996
20997    let edits = edits
20998        .into_iter()
20999        .map(|(range, edit)| {
21000            (
21001                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
21002                edit,
21003            )
21004        })
21005        .collect::<Vec<_>>();
21006
21007    let text_anchor_edits = edits
21008        .clone()
21009        .into_iter()
21010        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
21011        .collect::<Vec<_>>();
21012
21013    let edit_preview = window
21014        .update(cx, |_, _window, cx| {
21015            buffer
21016                .read(cx)
21017                .as_singleton()
21018                .unwrap()
21019                .read(cx)
21020                .preview_edits(text_anchor_edits.into(), cx)
21021        })
21022        .unwrap()
21023        .await;
21024
21025    cx.update(|_window, cx| {
21026        let highlighted_edits = edit_prediction_edit_text(
21027            snapshot.as_singleton().unwrap().2,
21028            &edits,
21029            &edit_preview,
21030            include_deletions,
21031            cx,
21032        );
21033        assertion_fn(highlighted_edits, cx)
21034    });
21035}
21036
21037#[track_caller]
21038fn assert_breakpoint(
21039    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
21040    path: &Arc<Path>,
21041    expected: Vec<(u32, Breakpoint)>,
21042) {
21043    if expected.len() == 0usize {
21044        assert!(!breakpoints.contains_key(path), "{}", path.display());
21045    } else {
21046        let mut breakpoint = breakpoints
21047            .get(path)
21048            .unwrap()
21049            .into_iter()
21050            .map(|breakpoint| {
21051                (
21052                    breakpoint.row,
21053                    Breakpoint {
21054                        message: breakpoint.message.clone(),
21055                        state: breakpoint.state,
21056                        condition: breakpoint.condition.clone(),
21057                        hit_condition: breakpoint.hit_condition.clone(),
21058                    },
21059                )
21060            })
21061            .collect::<Vec<_>>();
21062
21063        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
21064
21065        assert_eq!(expected, breakpoint);
21066    }
21067}
21068
21069fn add_log_breakpoint_at_cursor(
21070    editor: &mut Editor,
21071    log_message: &str,
21072    window: &mut Window,
21073    cx: &mut Context<Editor>,
21074) {
21075    let (anchor, bp) = editor
21076        .breakpoints_at_cursors(window, cx)
21077        .first()
21078        .and_then(|(anchor, bp)| {
21079            if let Some(bp) = bp {
21080                Some((*anchor, bp.clone()))
21081            } else {
21082                None
21083            }
21084        })
21085        .unwrap_or_else(|| {
21086            let cursor_position: Point = editor.selections.newest(cx).head();
21087
21088            let breakpoint_position = editor
21089                .snapshot(window, cx)
21090                .display_snapshot
21091                .buffer_snapshot
21092                .anchor_before(Point::new(cursor_position.row, 0));
21093
21094            (breakpoint_position, Breakpoint::new_log(log_message))
21095        });
21096
21097    editor.edit_breakpoint_at_anchor(
21098        anchor,
21099        bp,
21100        BreakpointEditAction::EditLogMessage(log_message.into()),
21101        cx,
21102    );
21103}
21104
21105#[gpui::test]
21106async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
21107    init_test(cx, |_| {});
21108
21109    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
21110    let fs = FakeFs::new(cx.executor());
21111    fs.insert_tree(
21112        path!("/a"),
21113        json!({
21114            "main.rs": sample_text,
21115        }),
21116    )
21117    .await;
21118    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21119    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21120    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21121
21122    let fs = FakeFs::new(cx.executor());
21123    fs.insert_tree(
21124        path!("/a"),
21125        json!({
21126            "main.rs": sample_text,
21127        }),
21128    )
21129    .await;
21130    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21131    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21132    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21133    let worktree_id = workspace
21134        .update(cx, |workspace, _window, cx| {
21135            workspace.project().update(cx, |project, cx| {
21136                project.worktrees(cx).next().unwrap().read(cx).id()
21137            })
21138        })
21139        .unwrap();
21140
21141    let buffer = project
21142        .update(cx, |project, cx| {
21143            project.open_buffer((worktree_id, "main.rs"), cx)
21144        })
21145        .await
21146        .unwrap();
21147
21148    let (editor, cx) = cx.add_window_view(|window, cx| {
21149        Editor::new(
21150            EditorMode::full(),
21151            MultiBuffer::build_from_buffer(buffer, cx),
21152            Some(project.clone()),
21153            window,
21154            cx,
21155        )
21156    });
21157
21158    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
21159    let abs_path = project.read_with(cx, |project, cx| {
21160        project
21161            .absolute_path(&project_path, cx)
21162            .map(|path_buf| Arc::from(path_buf.to_owned()))
21163            .unwrap()
21164    });
21165
21166    // assert we can add breakpoint on the first line
21167    editor.update_in(cx, |editor, window, cx| {
21168        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21169        editor.move_to_end(&MoveToEnd, window, cx);
21170        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21171    });
21172
21173    let breakpoints = editor.update(cx, |editor, cx| {
21174        editor
21175            .breakpoint_store()
21176            .as_ref()
21177            .unwrap()
21178            .read(cx)
21179            .all_source_breakpoints(cx)
21180            .clone()
21181    });
21182
21183    assert_eq!(1, breakpoints.len());
21184    assert_breakpoint(
21185        &breakpoints,
21186        &abs_path,
21187        vec![
21188            (0, Breakpoint::new_standard()),
21189            (3, Breakpoint::new_standard()),
21190        ],
21191    );
21192
21193    editor.update_in(cx, |editor, window, cx| {
21194        editor.move_to_beginning(&MoveToBeginning, window, cx);
21195        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21196    });
21197
21198    let breakpoints = editor.update(cx, |editor, cx| {
21199        editor
21200            .breakpoint_store()
21201            .as_ref()
21202            .unwrap()
21203            .read(cx)
21204            .all_source_breakpoints(cx)
21205            .clone()
21206    });
21207
21208    assert_eq!(1, breakpoints.len());
21209    assert_breakpoint(
21210        &breakpoints,
21211        &abs_path,
21212        vec![(3, Breakpoint::new_standard())],
21213    );
21214
21215    editor.update_in(cx, |editor, window, cx| {
21216        editor.move_to_end(&MoveToEnd, window, cx);
21217        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21218    });
21219
21220    let breakpoints = editor.update(cx, |editor, cx| {
21221        editor
21222            .breakpoint_store()
21223            .as_ref()
21224            .unwrap()
21225            .read(cx)
21226            .all_source_breakpoints(cx)
21227            .clone()
21228    });
21229
21230    assert_eq!(0, breakpoints.len());
21231    assert_breakpoint(&breakpoints, &abs_path, vec![]);
21232}
21233
21234#[gpui::test]
21235async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
21236    init_test(cx, |_| {});
21237
21238    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
21239
21240    let fs = FakeFs::new(cx.executor());
21241    fs.insert_tree(
21242        path!("/a"),
21243        json!({
21244            "main.rs": sample_text,
21245        }),
21246    )
21247    .await;
21248    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21249    let (workspace, cx) =
21250        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21251
21252    let worktree_id = workspace.update(cx, |workspace, cx| {
21253        workspace.project().update(cx, |project, cx| {
21254            project.worktrees(cx).next().unwrap().read(cx).id()
21255        })
21256    });
21257
21258    let buffer = project
21259        .update(cx, |project, cx| {
21260            project.open_buffer((worktree_id, "main.rs"), cx)
21261        })
21262        .await
21263        .unwrap();
21264
21265    let (editor, cx) = cx.add_window_view(|window, cx| {
21266        Editor::new(
21267            EditorMode::full(),
21268            MultiBuffer::build_from_buffer(buffer, cx),
21269            Some(project.clone()),
21270            window,
21271            cx,
21272        )
21273    });
21274
21275    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
21276    let abs_path = project.read_with(cx, |project, cx| {
21277        project
21278            .absolute_path(&project_path, cx)
21279            .map(|path_buf| Arc::from(path_buf.to_owned()))
21280            .unwrap()
21281    });
21282
21283    editor.update_in(cx, |editor, window, cx| {
21284        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
21285    });
21286
21287    let breakpoints = editor.update(cx, |editor, cx| {
21288        editor
21289            .breakpoint_store()
21290            .as_ref()
21291            .unwrap()
21292            .read(cx)
21293            .all_source_breakpoints(cx)
21294            .clone()
21295    });
21296
21297    assert_breakpoint(
21298        &breakpoints,
21299        &abs_path,
21300        vec![(0, Breakpoint::new_log("hello world"))],
21301    );
21302
21303    // Removing a log message from a log breakpoint should remove it
21304    editor.update_in(cx, |editor, window, cx| {
21305        add_log_breakpoint_at_cursor(editor, "", window, cx);
21306    });
21307
21308    let breakpoints = editor.update(cx, |editor, cx| {
21309        editor
21310            .breakpoint_store()
21311            .as_ref()
21312            .unwrap()
21313            .read(cx)
21314            .all_source_breakpoints(cx)
21315            .clone()
21316    });
21317
21318    assert_breakpoint(&breakpoints, &abs_path, vec![]);
21319
21320    editor.update_in(cx, |editor, window, cx| {
21321        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21322        editor.move_to_end(&MoveToEnd, window, cx);
21323        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21324        // Not adding a log message to a standard breakpoint shouldn't remove it
21325        add_log_breakpoint_at_cursor(editor, "", window, cx);
21326    });
21327
21328    let breakpoints = editor.update(cx, |editor, cx| {
21329        editor
21330            .breakpoint_store()
21331            .as_ref()
21332            .unwrap()
21333            .read(cx)
21334            .all_source_breakpoints(cx)
21335            .clone()
21336    });
21337
21338    assert_breakpoint(
21339        &breakpoints,
21340        &abs_path,
21341        vec![
21342            (0, Breakpoint::new_standard()),
21343            (3, Breakpoint::new_standard()),
21344        ],
21345    );
21346
21347    editor.update_in(cx, |editor, window, cx| {
21348        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
21349    });
21350
21351    let breakpoints = editor.update(cx, |editor, cx| {
21352        editor
21353            .breakpoint_store()
21354            .as_ref()
21355            .unwrap()
21356            .read(cx)
21357            .all_source_breakpoints(cx)
21358            .clone()
21359    });
21360
21361    assert_breakpoint(
21362        &breakpoints,
21363        &abs_path,
21364        vec![
21365            (0, Breakpoint::new_standard()),
21366            (3, Breakpoint::new_log("hello world")),
21367        ],
21368    );
21369
21370    editor.update_in(cx, |editor, window, cx| {
21371        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
21372    });
21373
21374    let breakpoints = editor.update(cx, |editor, cx| {
21375        editor
21376            .breakpoint_store()
21377            .as_ref()
21378            .unwrap()
21379            .read(cx)
21380            .all_source_breakpoints(cx)
21381            .clone()
21382    });
21383
21384    assert_breakpoint(
21385        &breakpoints,
21386        &abs_path,
21387        vec![
21388            (0, Breakpoint::new_standard()),
21389            (3, Breakpoint::new_log("hello Earth!!")),
21390        ],
21391    );
21392}
21393
21394/// This also tests that Editor::breakpoint_at_cursor_head is working properly
21395/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
21396/// or when breakpoints were placed out of order. This tests for a regression too
21397#[gpui::test]
21398async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
21399    init_test(cx, |_| {});
21400
21401    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
21402    let fs = FakeFs::new(cx.executor());
21403    fs.insert_tree(
21404        path!("/a"),
21405        json!({
21406            "main.rs": sample_text,
21407        }),
21408    )
21409    .await;
21410    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21411    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21412    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21413
21414    let fs = FakeFs::new(cx.executor());
21415    fs.insert_tree(
21416        path!("/a"),
21417        json!({
21418            "main.rs": sample_text,
21419        }),
21420    )
21421    .await;
21422    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21423    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21424    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21425    let worktree_id = workspace
21426        .update(cx, |workspace, _window, cx| {
21427            workspace.project().update(cx, |project, cx| {
21428                project.worktrees(cx).next().unwrap().read(cx).id()
21429            })
21430        })
21431        .unwrap();
21432
21433    let buffer = project
21434        .update(cx, |project, cx| {
21435            project.open_buffer((worktree_id, "main.rs"), cx)
21436        })
21437        .await
21438        .unwrap();
21439
21440    let (editor, cx) = cx.add_window_view(|window, cx| {
21441        Editor::new(
21442            EditorMode::full(),
21443            MultiBuffer::build_from_buffer(buffer, cx),
21444            Some(project.clone()),
21445            window,
21446            cx,
21447        )
21448    });
21449
21450    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
21451    let abs_path = project.read_with(cx, |project, cx| {
21452        project
21453            .absolute_path(&project_path, cx)
21454            .map(|path_buf| Arc::from(path_buf.to_owned()))
21455            .unwrap()
21456    });
21457
21458    // assert we can add breakpoint on the first line
21459    editor.update_in(cx, |editor, window, cx| {
21460        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21461        editor.move_to_end(&MoveToEnd, window, cx);
21462        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21463        editor.move_up(&MoveUp, window, cx);
21464        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21465    });
21466
21467    let breakpoints = editor.update(cx, |editor, cx| {
21468        editor
21469            .breakpoint_store()
21470            .as_ref()
21471            .unwrap()
21472            .read(cx)
21473            .all_source_breakpoints(cx)
21474            .clone()
21475    });
21476
21477    assert_eq!(1, breakpoints.len());
21478    assert_breakpoint(
21479        &breakpoints,
21480        &abs_path,
21481        vec![
21482            (0, Breakpoint::new_standard()),
21483            (2, Breakpoint::new_standard()),
21484            (3, Breakpoint::new_standard()),
21485        ],
21486    );
21487
21488    editor.update_in(cx, |editor, window, cx| {
21489        editor.move_to_beginning(&MoveToBeginning, window, cx);
21490        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21491        editor.move_to_end(&MoveToEnd, window, cx);
21492        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21493        // Disabling a breakpoint that doesn't exist should do nothing
21494        editor.move_up(&MoveUp, window, cx);
21495        editor.move_up(&MoveUp, window, cx);
21496        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21497    });
21498
21499    let breakpoints = editor.update(cx, |editor, cx| {
21500        editor
21501            .breakpoint_store()
21502            .as_ref()
21503            .unwrap()
21504            .read(cx)
21505            .all_source_breakpoints(cx)
21506            .clone()
21507    });
21508
21509    let disable_breakpoint = {
21510        let mut bp = Breakpoint::new_standard();
21511        bp.state = BreakpointState::Disabled;
21512        bp
21513    };
21514
21515    assert_eq!(1, breakpoints.len());
21516    assert_breakpoint(
21517        &breakpoints,
21518        &abs_path,
21519        vec![
21520            (0, disable_breakpoint.clone()),
21521            (2, Breakpoint::new_standard()),
21522            (3, disable_breakpoint.clone()),
21523        ],
21524    );
21525
21526    editor.update_in(cx, |editor, window, cx| {
21527        editor.move_to_beginning(&MoveToBeginning, window, cx);
21528        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
21529        editor.move_to_end(&MoveToEnd, window, cx);
21530        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
21531        editor.move_up(&MoveUp, window, cx);
21532        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21533    });
21534
21535    let breakpoints = editor.update(cx, |editor, cx| {
21536        editor
21537            .breakpoint_store()
21538            .as_ref()
21539            .unwrap()
21540            .read(cx)
21541            .all_source_breakpoints(cx)
21542            .clone()
21543    });
21544
21545    assert_eq!(1, breakpoints.len());
21546    assert_breakpoint(
21547        &breakpoints,
21548        &abs_path,
21549        vec![
21550            (0, Breakpoint::new_standard()),
21551            (2, disable_breakpoint),
21552            (3, Breakpoint::new_standard()),
21553        ],
21554    );
21555}
21556
21557#[gpui::test]
21558async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
21559    init_test(cx, |_| {});
21560    let capabilities = lsp::ServerCapabilities {
21561        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
21562            prepare_provider: Some(true),
21563            work_done_progress_options: Default::default(),
21564        })),
21565        ..Default::default()
21566    };
21567    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
21568
21569    cx.set_state(indoc! {"
21570        struct Fˇoo {}
21571    "});
21572
21573    cx.update_editor(|editor, _, cx| {
21574        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
21575        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
21576        editor.highlight_background::<DocumentHighlightRead>(
21577            &[highlight_range],
21578            |theme| theme.colors().editor_document_highlight_read_background,
21579            cx,
21580        );
21581    });
21582
21583    let mut prepare_rename_handler = cx
21584        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
21585            move |_, _, _| async move {
21586                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
21587                    start: lsp::Position {
21588                        line: 0,
21589                        character: 7,
21590                    },
21591                    end: lsp::Position {
21592                        line: 0,
21593                        character: 10,
21594                    },
21595                })))
21596            },
21597        );
21598    let prepare_rename_task = cx
21599        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
21600        .expect("Prepare rename was not started");
21601    prepare_rename_handler.next().await.unwrap();
21602    prepare_rename_task.await.expect("Prepare rename failed");
21603
21604    let mut rename_handler =
21605        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
21606            let edit = lsp::TextEdit {
21607                range: lsp::Range {
21608                    start: lsp::Position {
21609                        line: 0,
21610                        character: 7,
21611                    },
21612                    end: lsp::Position {
21613                        line: 0,
21614                        character: 10,
21615                    },
21616                },
21617                new_text: "FooRenamed".to_string(),
21618            };
21619            Ok(Some(lsp::WorkspaceEdit::new(
21620                // Specify the same edit twice
21621                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
21622            )))
21623        });
21624    let rename_task = cx
21625        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
21626        .expect("Confirm rename was not started");
21627    rename_handler.next().await.unwrap();
21628    rename_task.await.expect("Confirm rename failed");
21629    cx.run_until_parked();
21630
21631    // Despite two edits, only one is actually applied as those are identical
21632    cx.assert_editor_state(indoc! {"
21633        struct FooRenamedˇ {}
21634    "});
21635}
21636
21637#[gpui::test]
21638async fn test_rename_without_prepare(cx: &mut TestAppContext) {
21639    init_test(cx, |_| {});
21640    // These capabilities indicate that the server does not support prepare rename.
21641    let capabilities = lsp::ServerCapabilities {
21642        rename_provider: Some(lsp::OneOf::Left(true)),
21643        ..Default::default()
21644    };
21645    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
21646
21647    cx.set_state(indoc! {"
21648        struct Fˇoo {}
21649    "});
21650
21651    cx.update_editor(|editor, _window, cx| {
21652        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
21653        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
21654        editor.highlight_background::<DocumentHighlightRead>(
21655            &[highlight_range],
21656            |theme| theme.colors().editor_document_highlight_read_background,
21657            cx,
21658        );
21659    });
21660
21661    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
21662        .expect("Prepare rename was not started")
21663        .await
21664        .expect("Prepare rename failed");
21665
21666    let mut rename_handler =
21667        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
21668            let edit = lsp::TextEdit {
21669                range: lsp::Range {
21670                    start: lsp::Position {
21671                        line: 0,
21672                        character: 7,
21673                    },
21674                    end: lsp::Position {
21675                        line: 0,
21676                        character: 10,
21677                    },
21678                },
21679                new_text: "FooRenamed".to_string(),
21680            };
21681            Ok(Some(lsp::WorkspaceEdit::new(
21682                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
21683            )))
21684        });
21685    let rename_task = cx
21686        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
21687        .expect("Confirm rename was not started");
21688    rename_handler.next().await.unwrap();
21689    rename_task.await.expect("Confirm rename failed");
21690    cx.run_until_parked();
21691
21692    // Correct range is renamed, as `surrounding_word` is used to find it.
21693    cx.assert_editor_state(indoc! {"
21694        struct FooRenamedˇ {}
21695    "});
21696}
21697
21698#[gpui::test]
21699async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
21700    init_test(cx, |_| {});
21701    let mut cx = EditorTestContext::new(cx).await;
21702
21703    let language = Arc::new(
21704        Language::new(
21705            LanguageConfig::default(),
21706            Some(tree_sitter_html::LANGUAGE.into()),
21707        )
21708        .with_brackets_query(
21709            r#"
21710            ("<" @open "/>" @close)
21711            ("</" @open ">" @close)
21712            ("<" @open ">" @close)
21713            ("\"" @open "\"" @close)
21714            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
21715        "#,
21716        )
21717        .unwrap(),
21718    );
21719    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
21720
21721    cx.set_state(indoc! {"
21722        <span>ˇ</span>
21723    "});
21724    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21725    cx.assert_editor_state(indoc! {"
21726        <span>
21727        ˇ
21728        </span>
21729    "});
21730
21731    cx.set_state(indoc! {"
21732        <span><span></span>ˇ</span>
21733    "});
21734    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21735    cx.assert_editor_state(indoc! {"
21736        <span><span></span>
21737        ˇ</span>
21738    "});
21739
21740    cx.set_state(indoc! {"
21741        <span>ˇ
21742        </span>
21743    "});
21744    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21745    cx.assert_editor_state(indoc! {"
21746        <span>
21747        ˇ
21748        </span>
21749    "});
21750}
21751
21752#[gpui::test(iterations = 10)]
21753async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
21754    init_test(cx, |_| {});
21755
21756    let fs = FakeFs::new(cx.executor());
21757    fs.insert_tree(
21758        path!("/dir"),
21759        json!({
21760            "a.ts": "a",
21761        }),
21762    )
21763    .await;
21764
21765    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
21766    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21767    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21768
21769    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21770    language_registry.add(Arc::new(Language::new(
21771        LanguageConfig {
21772            name: "TypeScript".into(),
21773            matcher: LanguageMatcher {
21774                path_suffixes: vec!["ts".to_string()],
21775                ..Default::default()
21776            },
21777            ..Default::default()
21778        },
21779        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
21780    )));
21781    let mut fake_language_servers = language_registry.register_fake_lsp(
21782        "TypeScript",
21783        FakeLspAdapter {
21784            capabilities: lsp::ServerCapabilities {
21785                code_lens_provider: Some(lsp::CodeLensOptions {
21786                    resolve_provider: Some(true),
21787                }),
21788                execute_command_provider: Some(lsp::ExecuteCommandOptions {
21789                    commands: vec!["_the/command".to_string()],
21790                    ..lsp::ExecuteCommandOptions::default()
21791                }),
21792                ..lsp::ServerCapabilities::default()
21793            },
21794            ..FakeLspAdapter::default()
21795        },
21796    );
21797
21798    let editor = workspace
21799        .update(cx, |workspace, window, cx| {
21800            workspace.open_abs_path(
21801                PathBuf::from(path!("/dir/a.ts")),
21802                OpenOptions::default(),
21803                window,
21804                cx,
21805            )
21806        })
21807        .unwrap()
21808        .await
21809        .unwrap()
21810        .downcast::<Editor>()
21811        .unwrap();
21812    cx.executor().run_until_parked();
21813
21814    let fake_server = fake_language_servers.next().await.unwrap();
21815
21816    let buffer = editor.update(cx, |editor, cx| {
21817        editor
21818            .buffer()
21819            .read(cx)
21820            .as_singleton()
21821            .expect("have opened a single file by path")
21822    });
21823
21824    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
21825    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
21826    drop(buffer_snapshot);
21827    let actions = cx
21828        .update_window(*workspace, |_, window, cx| {
21829            project.code_actions(&buffer, anchor..anchor, window, cx)
21830        })
21831        .unwrap();
21832
21833    fake_server
21834        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
21835            Ok(Some(vec![
21836                lsp::CodeLens {
21837                    range: lsp::Range::default(),
21838                    command: Some(lsp::Command {
21839                        title: "Code lens command".to_owned(),
21840                        command: "_the/command".to_owned(),
21841                        arguments: None,
21842                    }),
21843                    data: None,
21844                },
21845                lsp::CodeLens {
21846                    range: lsp::Range::default(),
21847                    command: Some(lsp::Command {
21848                        title: "Command not in capabilities".to_owned(),
21849                        command: "not in capabilities".to_owned(),
21850                        arguments: None,
21851                    }),
21852                    data: None,
21853                },
21854                lsp::CodeLens {
21855                    range: lsp::Range {
21856                        start: lsp::Position {
21857                            line: 1,
21858                            character: 1,
21859                        },
21860                        end: lsp::Position {
21861                            line: 1,
21862                            character: 1,
21863                        },
21864                    },
21865                    command: Some(lsp::Command {
21866                        title: "Command not in range".to_owned(),
21867                        command: "_the/command".to_owned(),
21868                        arguments: None,
21869                    }),
21870                    data: None,
21871                },
21872            ]))
21873        })
21874        .next()
21875        .await;
21876
21877    let actions = actions.await.unwrap();
21878    assert_eq!(
21879        actions.len(),
21880        1,
21881        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
21882    );
21883    let action = actions[0].clone();
21884    let apply = project.update(cx, |project, cx| {
21885        project.apply_code_action(buffer.clone(), action, true, cx)
21886    });
21887
21888    // Resolving the code action does not populate its edits. In absence of
21889    // edits, we must execute the given command.
21890    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
21891        |mut lens, _| async move {
21892            let lens_command = lens.command.as_mut().expect("should have a command");
21893            assert_eq!(lens_command.title, "Code lens command");
21894            lens_command.arguments = Some(vec![json!("the-argument")]);
21895            Ok(lens)
21896        },
21897    );
21898
21899    // While executing the command, the language server sends the editor
21900    // a `workspaceEdit` request.
21901    fake_server
21902        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
21903            let fake = fake_server.clone();
21904            move |params, _| {
21905                assert_eq!(params.command, "_the/command");
21906                let fake = fake.clone();
21907                async move {
21908                    fake.server
21909                        .request::<lsp::request::ApplyWorkspaceEdit>(
21910                            lsp::ApplyWorkspaceEditParams {
21911                                label: None,
21912                                edit: lsp::WorkspaceEdit {
21913                                    changes: Some(
21914                                        [(
21915                                            lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
21916                                            vec![lsp::TextEdit {
21917                                                range: lsp::Range::new(
21918                                                    lsp::Position::new(0, 0),
21919                                                    lsp::Position::new(0, 0),
21920                                                ),
21921                                                new_text: "X".into(),
21922                                            }],
21923                                        )]
21924                                        .into_iter()
21925                                        .collect(),
21926                                    ),
21927                                    ..lsp::WorkspaceEdit::default()
21928                                },
21929                            },
21930                        )
21931                        .await
21932                        .into_response()
21933                        .unwrap();
21934                    Ok(Some(json!(null)))
21935                }
21936            }
21937        })
21938        .next()
21939        .await;
21940
21941    // Applying the code lens command returns a project transaction containing the edits
21942    // sent by the language server in its `workspaceEdit` request.
21943    let transaction = apply.await.unwrap();
21944    assert!(transaction.0.contains_key(&buffer));
21945    buffer.update(cx, |buffer, cx| {
21946        assert_eq!(buffer.text(), "Xa");
21947        buffer.undo(cx);
21948        assert_eq!(buffer.text(), "a");
21949    });
21950
21951    let actions_after_edits = cx
21952        .update_window(*workspace, |_, window, cx| {
21953            project.code_actions(&buffer, anchor..anchor, window, cx)
21954        })
21955        .unwrap()
21956        .await
21957        .unwrap();
21958    assert_eq!(
21959        actions, actions_after_edits,
21960        "For the same selection, same code lens actions should be returned"
21961    );
21962
21963    let _responses =
21964        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
21965            panic!("No more code lens requests are expected");
21966        });
21967    editor.update_in(cx, |editor, window, cx| {
21968        editor.select_all(&SelectAll, window, cx);
21969    });
21970    cx.executor().run_until_parked();
21971    let new_actions = cx
21972        .update_window(*workspace, |_, window, cx| {
21973            project.code_actions(&buffer, anchor..anchor, window, cx)
21974        })
21975        .unwrap()
21976        .await
21977        .unwrap();
21978    assert_eq!(
21979        actions, new_actions,
21980        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
21981    );
21982}
21983
21984#[gpui::test]
21985async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
21986    init_test(cx, |_| {});
21987
21988    let fs = FakeFs::new(cx.executor());
21989    let main_text = r#"fn main() {
21990println!("1");
21991println!("2");
21992println!("3");
21993println!("4");
21994println!("5");
21995}"#;
21996    let lib_text = "mod foo {}";
21997    fs.insert_tree(
21998        path!("/a"),
21999        json!({
22000            "lib.rs": lib_text,
22001            "main.rs": main_text,
22002        }),
22003    )
22004    .await;
22005
22006    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22007    let (workspace, cx) =
22008        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22009    let worktree_id = workspace.update(cx, |workspace, cx| {
22010        workspace.project().update(cx, |project, cx| {
22011            project.worktrees(cx).next().unwrap().read(cx).id()
22012        })
22013    });
22014
22015    let expected_ranges = vec![
22016        Point::new(0, 0)..Point::new(0, 0),
22017        Point::new(1, 0)..Point::new(1, 1),
22018        Point::new(2, 0)..Point::new(2, 2),
22019        Point::new(3, 0)..Point::new(3, 3),
22020    ];
22021
22022    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
22023    let editor_1 = workspace
22024        .update_in(cx, |workspace, window, cx| {
22025            workspace.open_path(
22026                (worktree_id, "main.rs"),
22027                Some(pane_1.downgrade()),
22028                true,
22029                window,
22030                cx,
22031            )
22032        })
22033        .unwrap()
22034        .await
22035        .downcast::<Editor>()
22036        .unwrap();
22037    pane_1.update(cx, |pane, cx| {
22038        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22039        open_editor.update(cx, |editor, cx| {
22040            assert_eq!(
22041                editor.display_text(cx),
22042                main_text,
22043                "Original main.rs text on initial open",
22044            );
22045            assert_eq!(
22046                editor
22047                    .selections
22048                    .all::<Point>(cx)
22049                    .into_iter()
22050                    .map(|s| s.range())
22051                    .collect::<Vec<_>>(),
22052                vec![Point::zero()..Point::zero()],
22053                "Default selections on initial open",
22054            );
22055        })
22056    });
22057    editor_1.update_in(cx, |editor, window, cx| {
22058        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22059            s.select_ranges(expected_ranges.clone());
22060        });
22061    });
22062
22063    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
22064        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
22065    });
22066    let editor_2 = workspace
22067        .update_in(cx, |workspace, window, cx| {
22068            workspace.open_path(
22069                (worktree_id, "main.rs"),
22070                Some(pane_2.downgrade()),
22071                true,
22072                window,
22073                cx,
22074            )
22075        })
22076        .unwrap()
22077        .await
22078        .downcast::<Editor>()
22079        .unwrap();
22080    pane_2.update(cx, |pane, cx| {
22081        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22082        open_editor.update(cx, |editor, cx| {
22083            assert_eq!(
22084                editor.display_text(cx),
22085                main_text,
22086                "Original main.rs text on initial open in another panel",
22087            );
22088            assert_eq!(
22089                editor
22090                    .selections
22091                    .all::<Point>(cx)
22092                    .into_iter()
22093                    .map(|s| s.range())
22094                    .collect::<Vec<_>>(),
22095                vec![Point::zero()..Point::zero()],
22096                "Default selections on initial open in another panel",
22097            );
22098        })
22099    });
22100
22101    editor_2.update_in(cx, |editor, window, cx| {
22102        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
22103    });
22104
22105    let _other_editor_1 = workspace
22106        .update_in(cx, |workspace, window, cx| {
22107            workspace.open_path(
22108                (worktree_id, "lib.rs"),
22109                Some(pane_1.downgrade()),
22110                true,
22111                window,
22112                cx,
22113            )
22114        })
22115        .unwrap()
22116        .await
22117        .downcast::<Editor>()
22118        .unwrap();
22119    pane_1
22120        .update_in(cx, |pane, window, cx| {
22121            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
22122        })
22123        .await
22124        .unwrap();
22125    drop(editor_1);
22126    pane_1.update(cx, |pane, cx| {
22127        pane.active_item()
22128            .unwrap()
22129            .downcast::<Editor>()
22130            .unwrap()
22131            .update(cx, |editor, cx| {
22132                assert_eq!(
22133                    editor.display_text(cx),
22134                    lib_text,
22135                    "Other file should be open and active",
22136                );
22137            });
22138        assert_eq!(pane.items().count(), 1, "No other editors should be open");
22139    });
22140
22141    let _other_editor_2 = workspace
22142        .update_in(cx, |workspace, window, cx| {
22143            workspace.open_path(
22144                (worktree_id, "lib.rs"),
22145                Some(pane_2.downgrade()),
22146                true,
22147                window,
22148                cx,
22149            )
22150        })
22151        .unwrap()
22152        .await
22153        .downcast::<Editor>()
22154        .unwrap();
22155    pane_2
22156        .update_in(cx, |pane, window, cx| {
22157            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
22158        })
22159        .await
22160        .unwrap();
22161    drop(editor_2);
22162    pane_2.update(cx, |pane, cx| {
22163        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22164        open_editor.update(cx, |editor, cx| {
22165            assert_eq!(
22166                editor.display_text(cx),
22167                lib_text,
22168                "Other file should be open and active in another panel too",
22169            );
22170        });
22171        assert_eq!(
22172            pane.items().count(),
22173            1,
22174            "No other editors should be open in another pane",
22175        );
22176    });
22177
22178    let _editor_1_reopened = workspace
22179        .update_in(cx, |workspace, window, cx| {
22180            workspace.open_path(
22181                (worktree_id, "main.rs"),
22182                Some(pane_1.downgrade()),
22183                true,
22184                window,
22185                cx,
22186            )
22187        })
22188        .unwrap()
22189        .await
22190        .downcast::<Editor>()
22191        .unwrap();
22192    let _editor_2_reopened = workspace
22193        .update_in(cx, |workspace, window, cx| {
22194            workspace.open_path(
22195                (worktree_id, "main.rs"),
22196                Some(pane_2.downgrade()),
22197                true,
22198                window,
22199                cx,
22200            )
22201        })
22202        .unwrap()
22203        .await
22204        .downcast::<Editor>()
22205        .unwrap();
22206    pane_1.update(cx, |pane, cx| {
22207        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22208        open_editor.update(cx, |editor, cx| {
22209            assert_eq!(
22210                editor.display_text(cx),
22211                main_text,
22212                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
22213            );
22214            assert_eq!(
22215                editor
22216                    .selections
22217                    .all::<Point>(cx)
22218                    .into_iter()
22219                    .map(|s| s.range())
22220                    .collect::<Vec<_>>(),
22221                expected_ranges,
22222                "Previous editor in the 1st panel had selections and should get them restored on reopen",
22223            );
22224        })
22225    });
22226    pane_2.update(cx, |pane, cx| {
22227        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22228        open_editor.update(cx, |editor, cx| {
22229            assert_eq!(
22230                editor.display_text(cx),
22231                r#"fn main() {
22232⋯rintln!("1");
22233⋯intln!("2");
22234⋯ntln!("3");
22235println!("4");
22236println!("5");
22237}"#,
22238                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
22239            );
22240            assert_eq!(
22241                editor
22242                    .selections
22243                    .all::<Point>(cx)
22244                    .into_iter()
22245                    .map(|s| s.range())
22246                    .collect::<Vec<_>>(),
22247                vec![Point::zero()..Point::zero()],
22248                "Previous editor in the 2nd pane had no selections changed hence should restore none",
22249            );
22250        })
22251    });
22252}
22253
22254#[gpui::test]
22255async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
22256    init_test(cx, |_| {});
22257
22258    let fs = FakeFs::new(cx.executor());
22259    let main_text = r#"fn main() {
22260println!("1");
22261println!("2");
22262println!("3");
22263println!("4");
22264println!("5");
22265}"#;
22266    let lib_text = "mod foo {}";
22267    fs.insert_tree(
22268        path!("/a"),
22269        json!({
22270            "lib.rs": lib_text,
22271            "main.rs": main_text,
22272        }),
22273    )
22274    .await;
22275
22276    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22277    let (workspace, cx) =
22278        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22279    let worktree_id = workspace.update(cx, |workspace, cx| {
22280        workspace.project().update(cx, |project, cx| {
22281            project.worktrees(cx).next().unwrap().read(cx).id()
22282        })
22283    });
22284
22285    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
22286    let editor = workspace
22287        .update_in(cx, |workspace, window, cx| {
22288            workspace.open_path(
22289                (worktree_id, "main.rs"),
22290                Some(pane.downgrade()),
22291                true,
22292                window,
22293                cx,
22294            )
22295        })
22296        .unwrap()
22297        .await
22298        .downcast::<Editor>()
22299        .unwrap();
22300    pane.update(cx, |pane, cx| {
22301        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22302        open_editor.update(cx, |editor, cx| {
22303            assert_eq!(
22304                editor.display_text(cx),
22305                main_text,
22306                "Original main.rs text on initial open",
22307            );
22308        })
22309    });
22310    editor.update_in(cx, |editor, window, cx| {
22311        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
22312    });
22313
22314    cx.update_global(|store: &mut SettingsStore, cx| {
22315        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
22316            s.restore_on_file_reopen = Some(false);
22317        });
22318    });
22319    editor.update_in(cx, |editor, window, cx| {
22320        editor.fold_ranges(
22321            vec![
22322                Point::new(1, 0)..Point::new(1, 1),
22323                Point::new(2, 0)..Point::new(2, 2),
22324                Point::new(3, 0)..Point::new(3, 3),
22325            ],
22326            false,
22327            window,
22328            cx,
22329        );
22330    });
22331    pane.update_in(cx, |pane, window, cx| {
22332        pane.close_all_items(&CloseAllItems::default(), window, cx)
22333    })
22334    .await
22335    .unwrap();
22336    pane.update(cx, |pane, _| {
22337        assert!(pane.active_item().is_none());
22338    });
22339    cx.update_global(|store: &mut SettingsStore, cx| {
22340        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
22341            s.restore_on_file_reopen = Some(true);
22342        });
22343    });
22344
22345    let _editor_reopened = workspace
22346        .update_in(cx, |workspace, window, cx| {
22347            workspace.open_path(
22348                (worktree_id, "main.rs"),
22349                Some(pane.downgrade()),
22350                true,
22351                window,
22352                cx,
22353            )
22354        })
22355        .unwrap()
22356        .await
22357        .downcast::<Editor>()
22358        .unwrap();
22359    pane.update(cx, |pane, cx| {
22360        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22361        open_editor.update(cx, |editor, cx| {
22362            assert_eq!(
22363                editor.display_text(cx),
22364                main_text,
22365                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
22366            );
22367        })
22368    });
22369}
22370
22371#[gpui::test]
22372async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
22373    struct EmptyModalView {
22374        focus_handle: gpui::FocusHandle,
22375    }
22376    impl EventEmitter<DismissEvent> for EmptyModalView {}
22377    impl Render for EmptyModalView {
22378        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
22379            div()
22380        }
22381    }
22382    impl Focusable for EmptyModalView {
22383        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
22384            self.focus_handle.clone()
22385        }
22386    }
22387    impl workspace::ModalView for EmptyModalView {}
22388    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
22389        EmptyModalView {
22390            focus_handle: cx.focus_handle(),
22391        }
22392    }
22393
22394    init_test(cx, |_| {});
22395
22396    let fs = FakeFs::new(cx.executor());
22397    let project = Project::test(fs, [], cx).await;
22398    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22399    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
22400    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22401    let editor = cx.new_window_entity(|window, cx| {
22402        Editor::new(
22403            EditorMode::full(),
22404            buffer,
22405            Some(project.clone()),
22406            window,
22407            cx,
22408        )
22409    });
22410    workspace
22411        .update(cx, |workspace, window, cx| {
22412            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
22413        })
22414        .unwrap();
22415    editor.update_in(cx, |editor, window, cx| {
22416        editor.open_context_menu(&OpenContextMenu, window, cx);
22417        assert!(editor.mouse_context_menu.is_some());
22418    });
22419    workspace
22420        .update(cx, |workspace, window, cx| {
22421            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
22422        })
22423        .unwrap();
22424    cx.read(|cx| {
22425        assert!(editor.read(cx).mouse_context_menu.is_none());
22426    });
22427}
22428
22429#[gpui::test]
22430async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
22431    init_test(cx, |_| {});
22432
22433    let fs = FakeFs::new(cx.executor());
22434    fs.insert_file(path!("/file.html"), Default::default())
22435        .await;
22436
22437    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
22438
22439    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22440    let html_language = Arc::new(Language::new(
22441        LanguageConfig {
22442            name: "HTML".into(),
22443            matcher: LanguageMatcher {
22444                path_suffixes: vec!["html".to_string()],
22445                ..LanguageMatcher::default()
22446            },
22447            brackets: BracketPairConfig {
22448                pairs: vec![BracketPair {
22449                    start: "<".into(),
22450                    end: ">".into(),
22451                    close: true,
22452                    ..Default::default()
22453                }],
22454                ..Default::default()
22455            },
22456            ..Default::default()
22457        },
22458        Some(tree_sitter_html::LANGUAGE.into()),
22459    ));
22460    language_registry.add(html_language);
22461    let mut fake_servers = language_registry.register_fake_lsp(
22462        "HTML",
22463        FakeLspAdapter {
22464            capabilities: lsp::ServerCapabilities {
22465                completion_provider: Some(lsp::CompletionOptions {
22466                    resolve_provider: Some(true),
22467                    ..Default::default()
22468                }),
22469                ..Default::default()
22470            },
22471            ..Default::default()
22472        },
22473    );
22474
22475    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22476    let cx = &mut VisualTestContext::from_window(*workspace, cx);
22477
22478    let worktree_id = workspace
22479        .update(cx, |workspace, _window, cx| {
22480            workspace.project().update(cx, |project, cx| {
22481                project.worktrees(cx).next().unwrap().read(cx).id()
22482            })
22483        })
22484        .unwrap();
22485    project
22486        .update(cx, |project, cx| {
22487            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
22488        })
22489        .await
22490        .unwrap();
22491    let editor = workspace
22492        .update(cx, |workspace, window, cx| {
22493            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
22494        })
22495        .unwrap()
22496        .await
22497        .unwrap()
22498        .downcast::<Editor>()
22499        .unwrap();
22500
22501    let fake_server = fake_servers.next().await.unwrap();
22502    editor.update_in(cx, |editor, window, cx| {
22503        editor.set_text("<ad></ad>", window, cx);
22504        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22505            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
22506        });
22507        let Some((buffer, _)) = editor
22508            .buffer
22509            .read(cx)
22510            .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
22511        else {
22512            panic!("Failed to get buffer for selection position");
22513        };
22514        let buffer = buffer.read(cx);
22515        let buffer_id = buffer.remote_id();
22516        let opening_range =
22517            buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
22518        let closing_range =
22519            buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
22520        let mut linked_ranges = HashMap::default();
22521        linked_ranges.insert(
22522            buffer_id,
22523            vec![(opening_range.clone(), vec![closing_range.clone()])],
22524        );
22525        editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
22526    });
22527    let mut completion_handle =
22528        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
22529            Ok(Some(lsp::CompletionResponse::Array(vec![
22530                lsp::CompletionItem {
22531                    label: "head".to_string(),
22532                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22533                        lsp::InsertReplaceEdit {
22534                            new_text: "head".to_string(),
22535                            insert: lsp::Range::new(
22536                                lsp::Position::new(0, 1),
22537                                lsp::Position::new(0, 3),
22538                            ),
22539                            replace: lsp::Range::new(
22540                                lsp::Position::new(0, 1),
22541                                lsp::Position::new(0, 3),
22542                            ),
22543                        },
22544                    )),
22545                    ..Default::default()
22546                },
22547            ])))
22548        });
22549    editor.update_in(cx, |editor, window, cx| {
22550        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
22551    });
22552    cx.run_until_parked();
22553    completion_handle.next().await.unwrap();
22554    editor.update(cx, |editor, _| {
22555        assert!(
22556            editor.context_menu_visible(),
22557            "Completion menu should be visible"
22558        );
22559    });
22560    editor.update_in(cx, |editor, window, cx| {
22561        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
22562    });
22563    cx.executor().run_until_parked();
22564    editor.update(cx, |editor, cx| {
22565        assert_eq!(editor.text(cx), "<head></head>");
22566    });
22567}
22568
22569#[gpui::test]
22570async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
22571    init_test(cx, |_| {});
22572
22573    let fs = FakeFs::new(cx.executor());
22574    fs.insert_tree(
22575        path!("/root"),
22576        json!({
22577            "a": {
22578                "main.rs": "fn main() {}",
22579            },
22580            "foo": {
22581                "bar": {
22582                    "external_file.rs": "pub mod external {}",
22583                }
22584            }
22585        }),
22586    )
22587    .await;
22588
22589    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
22590    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22591    language_registry.add(rust_lang());
22592    let _fake_servers = language_registry.register_fake_lsp(
22593        "Rust",
22594        FakeLspAdapter {
22595            ..FakeLspAdapter::default()
22596        },
22597    );
22598    let (workspace, cx) =
22599        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22600    let worktree_id = workspace.update(cx, |workspace, cx| {
22601        workspace.project().update(cx, |project, cx| {
22602            project.worktrees(cx).next().unwrap().read(cx).id()
22603        })
22604    });
22605
22606    let assert_language_servers_count =
22607        |expected: usize, context: &str, cx: &mut VisualTestContext| {
22608            project.update(cx, |project, cx| {
22609                let current = project
22610                    .lsp_store()
22611                    .read(cx)
22612                    .as_local()
22613                    .unwrap()
22614                    .language_servers
22615                    .len();
22616                assert_eq!(expected, current, "{context}");
22617            });
22618        };
22619
22620    assert_language_servers_count(
22621        0,
22622        "No servers should be running before any file is open",
22623        cx,
22624    );
22625    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
22626    let main_editor = workspace
22627        .update_in(cx, |workspace, window, cx| {
22628            workspace.open_path(
22629                (worktree_id, "main.rs"),
22630                Some(pane.downgrade()),
22631                true,
22632                window,
22633                cx,
22634            )
22635        })
22636        .unwrap()
22637        .await
22638        .downcast::<Editor>()
22639        .unwrap();
22640    pane.update(cx, |pane, cx| {
22641        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22642        open_editor.update(cx, |editor, cx| {
22643            assert_eq!(
22644                editor.display_text(cx),
22645                "fn main() {}",
22646                "Original main.rs text on initial open",
22647            );
22648        });
22649        assert_eq!(open_editor, main_editor);
22650    });
22651    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
22652
22653    let external_editor = workspace
22654        .update_in(cx, |workspace, window, cx| {
22655            workspace.open_abs_path(
22656                PathBuf::from("/root/foo/bar/external_file.rs"),
22657                OpenOptions::default(),
22658                window,
22659                cx,
22660            )
22661        })
22662        .await
22663        .expect("opening external file")
22664        .downcast::<Editor>()
22665        .expect("downcasted external file's open element to editor");
22666    pane.update(cx, |pane, cx| {
22667        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22668        open_editor.update(cx, |editor, cx| {
22669            assert_eq!(
22670                editor.display_text(cx),
22671                "pub mod external {}",
22672                "External file is open now",
22673            );
22674        });
22675        assert_eq!(open_editor, external_editor);
22676    });
22677    assert_language_servers_count(
22678        1,
22679        "Second, external, *.rs file should join the existing server",
22680        cx,
22681    );
22682
22683    pane.update_in(cx, |pane, window, cx| {
22684        pane.close_active_item(&CloseActiveItem::default(), window, cx)
22685    })
22686    .await
22687    .unwrap();
22688    pane.update_in(cx, |pane, window, cx| {
22689        pane.navigate_backward(window, cx);
22690    });
22691    cx.run_until_parked();
22692    pane.update(cx, |pane, cx| {
22693        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22694        open_editor.update(cx, |editor, cx| {
22695            assert_eq!(
22696                editor.display_text(cx),
22697                "pub mod external {}",
22698                "External file is open now",
22699            );
22700        });
22701    });
22702    assert_language_servers_count(
22703        1,
22704        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
22705        cx,
22706    );
22707
22708    cx.update(|_, cx| {
22709        workspace::reload(cx);
22710    });
22711    assert_language_servers_count(
22712        1,
22713        "After reloading the worktree with local and external files opened, only one project should be started",
22714        cx,
22715    );
22716}
22717
22718#[gpui::test]
22719async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
22720    init_test(cx, |_| {});
22721
22722    let mut cx = EditorTestContext::new(cx).await;
22723    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22724    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22725
22726    // test cursor move to start of each line on tab
22727    // for `if`, `elif`, `else`, `while`, `with` and `for`
22728    cx.set_state(indoc! {"
22729        def main():
22730        ˇ    for item in items:
22731        ˇ        while item.active:
22732        ˇ            if item.value > 10:
22733        ˇ                continue
22734        ˇ            elif item.value < 0:
22735        ˇ                break
22736        ˇ            else:
22737        ˇ                with item.context() as ctx:
22738        ˇ                    yield count
22739        ˇ        else:
22740        ˇ            log('while else')
22741        ˇ    else:
22742        ˇ        log('for else')
22743    "});
22744    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22745    cx.assert_editor_state(indoc! {"
22746        def main():
22747            ˇfor item in items:
22748                ˇwhile item.active:
22749                    ˇif item.value > 10:
22750                        ˇcontinue
22751                    ˇelif item.value < 0:
22752                        ˇbreak
22753                    ˇelse:
22754                        ˇwith item.context() as ctx:
22755                            ˇyield count
22756                ˇelse:
22757                    ˇlog('while else')
22758            ˇelse:
22759                ˇlog('for else')
22760    "});
22761    // test relative indent is preserved when tab
22762    // for `if`, `elif`, `else`, `while`, `with` and `for`
22763    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22764    cx.assert_editor_state(indoc! {"
22765        def main():
22766                ˇfor item in items:
22767                    ˇwhile item.active:
22768                        ˇif item.value > 10:
22769                            ˇcontinue
22770                        ˇelif item.value < 0:
22771                            ˇbreak
22772                        ˇelse:
22773                            ˇwith item.context() as ctx:
22774                                ˇyield count
22775                    ˇelse:
22776                        ˇlog('while else')
22777                ˇelse:
22778                    ˇlog('for else')
22779    "});
22780
22781    // test cursor move to start of each line on tab
22782    // for `try`, `except`, `else`, `finally`, `match` and `def`
22783    cx.set_state(indoc! {"
22784        def main():
22785        ˇ    try:
22786        ˇ        fetch()
22787        ˇ    except ValueError:
22788        ˇ        handle_error()
22789        ˇ    else:
22790        ˇ        match value:
22791        ˇ            case _:
22792        ˇ    finally:
22793        ˇ        def status():
22794        ˇ            return 0
22795    "});
22796    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22797    cx.assert_editor_state(indoc! {"
22798        def main():
22799            ˇtry:
22800                ˇfetch()
22801            ˇexcept ValueError:
22802                ˇhandle_error()
22803            ˇelse:
22804                ˇmatch value:
22805                    ˇcase _:
22806            ˇfinally:
22807                ˇdef status():
22808                    ˇreturn 0
22809    "});
22810    // test relative indent is preserved when tab
22811    // for `try`, `except`, `else`, `finally`, `match` and `def`
22812    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22813    cx.assert_editor_state(indoc! {"
22814        def main():
22815                ˇtry:
22816                    ˇfetch()
22817                ˇexcept ValueError:
22818                    ˇhandle_error()
22819                ˇelse:
22820                    ˇmatch value:
22821                        ˇcase _:
22822                ˇfinally:
22823                    ˇdef status():
22824                        ˇreturn 0
22825    "});
22826}
22827
22828#[gpui::test]
22829async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
22830    init_test(cx, |_| {});
22831
22832    let mut cx = EditorTestContext::new(cx).await;
22833    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22834    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22835
22836    // test `else` auto outdents when typed inside `if` block
22837    cx.set_state(indoc! {"
22838        def main():
22839            if i == 2:
22840                return
22841                ˇ
22842    "});
22843    cx.update_editor(|editor, window, cx| {
22844        editor.handle_input("else:", window, cx);
22845    });
22846    cx.assert_editor_state(indoc! {"
22847        def main():
22848            if i == 2:
22849                return
22850            else:ˇ
22851    "});
22852
22853    // test `except` auto outdents when typed inside `try` block
22854    cx.set_state(indoc! {"
22855        def main():
22856            try:
22857                i = 2
22858                ˇ
22859    "});
22860    cx.update_editor(|editor, window, cx| {
22861        editor.handle_input("except:", window, cx);
22862    });
22863    cx.assert_editor_state(indoc! {"
22864        def main():
22865            try:
22866                i = 2
22867            except:ˇ
22868    "});
22869
22870    // test `else` auto outdents when typed inside `except` block
22871    cx.set_state(indoc! {"
22872        def main():
22873            try:
22874                i = 2
22875            except:
22876                j = 2
22877                ˇ
22878    "});
22879    cx.update_editor(|editor, window, cx| {
22880        editor.handle_input("else:", window, cx);
22881    });
22882    cx.assert_editor_state(indoc! {"
22883        def main():
22884            try:
22885                i = 2
22886            except:
22887                j = 2
22888            else:ˇ
22889    "});
22890
22891    // test `finally` auto outdents when typed inside `else` block
22892    cx.set_state(indoc! {"
22893        def main():
22894            try:
22895                i = 2
22896            except:
22897                j = 2
22898            else:
22899                k = 2
22900                ˇ
22901    "});
22902    cx.update_editor(|editor, window, cx| {
22903        editor.handle_input("finally:", window, cx);
22904    });
22905    cx.assert_editor_state(indoc! {"
22906        def main():
22907            try:
22908                i = 2
22909            except:
22910                j = 2
22911            else:
22912                k = 2
22913            finally:ˇ
22914    "});
22915
22916    // test `else` does not outdents when typed inside `except` block right after for block
22917    cx.set_state(indoc! {"
22918        def main():
22919            try:
22920                i = 2
22921            except:
22922                for i in range(n):
22923                    pass
22924                ˇ
22925    "});
22926    cx.update_editor(|editor, window, cx| {
22927        editor.handle_input("else:", window, cx);
22928    });
22929    cx.assert_editor_state(indoc! {"
22930        def main():
22931            try:
22932                i = 2
22933            except:
22934                for i in range(n):
22935                    pass
22936                else:ˇ
22937    "});
22938
22939    // test `finally` auto outdents when typed inside `else` block right after for block
22940    cx.set_state(indoc! {"
22941        def main():
22942            try:
22943                i = 2
22944            except:
22945                j = 2
22946            else:
22947                for i in range(n):
22948                    pass
22949                ˇ
22950    "});
22951    cx.update_editor(|editor, window, cx| {
22952        editor.handle_input("finally:", window, cx);
22953    });
22954    cx.assert_editor_state(indoc! {"
22955        def main():
22956            try:
22957                i = 2
22958            except:
22959                j = 2
22960            else:
22961                for i in range(n):
22962                    pass
22963            finally:ˇ
22964    "});
22965
22966    // test `except` outdents to inner "try" block
22967    cx.set_state(indoc! {"
22968        def main():
22969            try:
22970                i = 2
22971                if i == 2:
22972                    try:
22973                        i = 3
22974                        ˇ
22975    "});
22976    cx.update_editor(|editor, window, cx| {
22977        editor.handle_input("except:", window, cx);
22978    });
22979    cx.assert_editor_state(indoc! {"
22980        def main():
22981            try:
22982                i = 2
22983                if i == 2:
22984                    try:
22985                        i = 3
22986                    except:ˇ
22987    "});
22988
22989    // test `except` outdents to outer "try" block
22990    cx.set_state(indoc! {"
22991        def main():
22992            try:
22993                i = 2
22994                if i == 2:
22995                    try:
22996                        i = 3
22997                ˇ
22998    "});
22999    cx.update_editor(|editor, window, cx| {
23000        editor.handle_input("except:", window, cx);
23001    });
23002    cx.assert_editor_state(indoc! {"
23003        def main():
23004            try:
23005                i = 2
23006                if i == 2:
23007                    try:
23008                        i = 3
23009            except:ˇ
23010    "});
23011
23012    // test `else` stays at correct indent when typed after `for` block
23013    cx.set_state(indoc! {"
23014        def main():
23015            for i in range(10):
23016                if i == 3:
23017                    break
23018            ˇ
23019    "});
23020    cx.update_editor(|editor, window, cx| {
23021        editor.handle_input("else:", window, cx);
23022    });
23023    cx.assert_editor_state(indoc! {"
23024        def main():
23025            for i in range(10):
23026                if i == 3:
23027                    break
23028            else:ˇ
23029    "});
23030
23031    // test does not outdent on typing after line with square brackets
23032    cx.set_state(indoc! {"
23033        def f() -> list[str]:
23034            ˇ
23035    "});
23036    cx.update_editor(|editor, window, cx| {
23037        editor.handle_input("a", window, cx);
23038    });
23039    cx.assert_editor_state(indoc! {"
23040        def f() -> list[str]:
2304123042    "});
23043
23044    // test does not outdent on typing : after case keyword
23045    cx.set_state(indoc! {"
23046        match 1:
23047            caseˇ
23048    "});
23049    cx.update_editor(|editor, window, cx| {
23050        editor.handle_input(":", window, cx);
23051    });
23052    cx.assert_editor_state(indoc! {"
23053        match 1:
23054            case:ˇ
23055    "});
23056}
23057
23058#[gpui::test]
23059async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
23060    init_test(cx, |_| {});
23061    update_test_language_settings(cx, |settings| {
23062        settings.defaults.extend_comment_on_newline = Some(false);
23063    });
23064    let mut cx = EditorTestContext::new(cx).await;
23065    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
23066    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23067
23068    // test correct indent after newline on comment
23069    cx.set_state(indoc! {"
23070        # COMMENT:ˇ
23071    "});
23072    cx.update_editor(|editor, window, cx| {
23073        editor.newline(&Newline, window, cx);
23074    });
23075    cx.assert_editor_state(indoc! {"
23076        # COMMENT:
23077        ˇ
23078    "});
23079
23080    // test correct indent after newline in brackets
23081    cx.set_state(indoc! {"
23082        {ˇ}
23083    "});
23084    cx.update_editor(|editor, window, cx| {
23085        editor.newline(&Newline, window, cx);
23086    });
23087    cx.run_until_parked();
23088    cx.assert_editor_state(indoc! {"
23089        {
23090            ˇ
23091        }
23092    "});
23093
23094    cx.set_state(indoc! {"
23095        (ˇ)
23096    "});
23097    cx.update_editor(|editor, window, cx| {
23098        editor.newline(&Newline, window, cx);
23099    });
23100    cx.run_until_parked();
23101    cx.assert_editor_state(indoc! {"
23102        (
23103            ˇ
23104        )
23105    "});
23106
23107    // do not indent after empty lists or dictionaries
23108    cx.set_state(indoc! {"
23109        a = []ˇ
23110    "});
23111    cx.update_editor(|editor, window, cx| {
23112        editor.newline(&Newline, window, cx);
23113    });
23114    cx.run_until_parked();
23115    cx.assert_editor_state(indoc! {"
23116        a = []
23117        ˇ
23118    "});
23119}
23120
23121#[gpui::test]
23122async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
23123    init_test(cx, |_| {});
23124
23125    let mut cx = EditorTestContext::new(cx).await;
23126    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
23127    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23128
23129    // test cursor move to start of each line on tab
23130    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
23131    cx.set_state(indoc! {"
23132        function main() {
23133        ˇ    for item in $items; do
23134        ˇ        while [ -n \"$item\" ]; do
23135        ˇ            if [ \"$value\" -gt 10 ]; then
23136        ˇ                continue
23137        ˇ            elif [ \"$value\" -lt 0 ]; then
23138        ˇ                break
23139        ˇ            else
23140        ˇ                echo \"$item\"
23141        ˇ            fi
23142        ˇ        done
23143        ˇ    done
23144        ˇ}
23145    "});
23146    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
23147    cx.assert_editor_state(indoc! {"
23148        function main() {
23149            ˇfor item in $items; do
23150                ˇwhile [ -n \"$item\" ]; do
23151                    ˇif [ \"$value\" -gt 10 ]; then
23152                        ˇcontinue
23153                    ˇelif [ \"$value\" -lt 0 ]; then
23154                        ˇbreak
23155                    ˇelse
23156                        ˇecho \"$item\"
23157                    ˇfi
23158                ˇdone
23159            ˇdone
23160        ˇ}
23161    "});
23162    // test relative indent is preserved when tab
23163    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
23164    cx.assert_editor_state(indoc! {"
23165        function main() {
23166                ˇfor item in $items; do
23167                    ˇwhile [ -n \"$item\" ]; do
23168                        ˇif [ \"$value\" -gt 10 ]; then
23169                            ˇcontinue
23170                        ˇelif [ \"$value\" -lt 0 ]; then
23171                            ˇbreak
23172                        ˇelse
23173                            ˇecho \"$item\"
23174                        ˇfi
23175                    ˇdone
23176                ˇdone
23177            ˇ}
23178    "});
23179
23180    // test cursor move to start of each line on tab
23181    // for `case` statement with patterns
23182    cx.set_state(indoc! {"
23183        function handle() {
23184        ˇ    case \"$1\" in
23185        ˇ        start)
23186        ˇ            echo \"a\"
23187        ˇ            ;;
23188        ˇ        stop)
23189        ˇ            echo \"b\"
23190        ˇ            ;;
23191        ˇ        *)
23192        ˇ            echo \"c\"
23193        ˇ            ;;
23194        ˇ    esac
23195        ˇ}
23196    "});
23197    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
23198    cx.assert_editor_state(indoc! {"
23199        function handle() {
23200            ˇcase \"$1\" in
23201                ˇstart)
23202                    ˇecho \"a\"
23203                    ˇ;;
23204                ˇstop)
23205                    ˇecho \"b\"
23206                    ˇ;;
23207                ˇ*)
23208                    ˇecho \"c\"
23209                    ˇ;;
23210            ˇesac
23211        ˇ}
23212    "});
23213}
23214
23215#[gpui::test]
23216async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
23217    init_test(cx, |_| {});
23218
23219    let mut cx = EditorTestContext::new(cx).await;
23220    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
23221    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23222
23223    // test indents on comment insert
23224    cx.set_state(indoc! {"
23225        function main() {
23226        ˇ    for item in $items; do
23227        ˇ        while [ -n \"$item\" ]; do
23228        ˇ            if [ \"$value\" -gt 10 ]; then
23229        ˇ                continue
23230        ˇ            elif [ \"$value\" -lt 0 ]; then
23231        ˇ                break
23232        ˇ            else
23233        ˇ                echo \"$item\"
23234        ˇ            fi
23235        ˇ        done
23236        ˇ    done
23237        ˇ}
23238    "});
23239    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
23240    cx.assert_editor_state(indoc! {"
23241        function main() {
23242        #ˇ    for item in $items; do
23243        #ˇ        while [ -n \"$item\" ]; do
23244        #ˇ            if [ \"$value\" -gt 10 ]; then
23245        #ˇ                continue
23246        #ˇ            elif [ \"$value\" -lt 0 ]; then
23247        #ˇ                break
23248        #ˇ            else
23249        #ˇ                echo \"$item\"
23250        #ˇ            fi
23251        #ˇ        done
23252        #ˇ    done
23253        #ˇ}
23254    "});
23255}
23256
23257#[gpui::test]
23258async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
23259    init_test(cx, |_| {});
23260
23261    let mut cx = EditorTestContext::new(cx).await;
23262    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
23263    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23264
23265    // test `else` auto outdents when typed inside `if` block
23266    cx.set_state(indoc! {"
23267        if [ \"$1\" = \"test\" ]; then
23268            echo \"foo bar\"
23269            ˇ
23270    "});
23271    cx.update_editor(|editor, window, cx| {
23272        editor.handle_input("else", window, cx);
23273    });
23274    cx.assert_editor_state(indoc! {"
23275        if [ \"$1\" = \"test\" ]; then
23276            echo \"foo bar\"
23277        elseˇ
23278    "});
23279
23280    // test `elif` auto outdents when typed inside `if` block
23281    cx.set_state(indoc! {"
23282        if [ \"$1\" = \"test\" ]; then
23283            echo \"foo bar\"
23284            ˇ
23285    "});
23286    cx.update_editor(|editor, window, cx| {
23287        editor.handle_input("elif", window, cx);
23288    });
23289    cx.assert_editor_state(indoc! {"
23290        if [ \"$1\" = \"test\" ]; then
23291            echo \"foo bar\"
23292        elifˇ
23293    "});
23294
23295    // test `fi` auto outdents when typed inside `else` block
23296    cx.set_state(indoc! {"
23297        if [ \"$1\" = \"test\" ]; then
23298            echo \"foo bar\"
23299        else
23300            echo \"bar baz\"
23301            ˇ
23302    "});
23303    cx.update_editor(|editor, window, cx| {
23304        editor.handle_input("fi", window, cx);
23305    });
23306    cx.assert_editor_state(indoc! {"
23307        if [ \"$1\" = \"test\" ]; then
23308            echo \"foo bar\"
23309        else
23310            echo \"bar baz\"
23311        fiˇ
23312    "});
23313
23314    // test `done` auto outdents when typed inside `while` block
23315    cx.set_state(indoc! {"
23316        while read line; do
23317            echo \"$line\"
23318            ˇ
23319    "});
23320    cx.update_editor(|editor, window, cx| {
23321        editor.handle_input("done", window, cx);
23322    });
23323    cx.assert_editor_state(indoc! {"
23324        while read line; do
23325            echo \"$line\"
23326        doneˇ
23327    "});
23328
23329    // test `done` auto outdents when typed inside `for` block
23330    cx.set_state(indoc! {"
23331        for file in *.txt; do
23332            cat \"$file\"
23333            ˇ
23334    "});
23335    cx.update_editor(|editor, window, cx| {
23336        editor.handle_input("done", window, cx);
23337    });
23338    cx.assert_editor_state(indoc! {"
23339        for file in *.txt; do
23340            cat \"$file\"
23341        doneˇ
23342    "});
23343
23344    // test `esac` auto outdents when typed inside `case` block
23345    cx.set_state(indoc! {"
23346        case \"$1\" in
23347            start)
23348                echo \"foo bar\"
23349                ;;
23350            stop)
23351                echo \"bar baz\"
23352                ;;
23353            ˇ
23354    "});
23355    cx.update_editor(|editor, window, cx| {
23356        editor.handle_input("esac", window, cx);
23357    });
23358    cx.assert_editor_state(indoc! {"
23359        case \"$1\" in
23360            start)
23361                echo \"foo bar\"
23362                ;;
23363            stop)
23364                echo \"bar baz\"
23365                ;;
23366        esacˇ
23367    "});
23368
23369    // test `*)` auto outdents when typed inside `case` block
23370    cx.set_state(indoc! {"
23371        case \"$1\" in
23372            start)
23373                echo \"foo bar\"
23374                ;;
23375                ˇ
23376    "});
23377    cx.update_editor(|editor, window, cx| {
23378        editor.handle_input("*)", window, cx);
23379    });
23380    cx.assert_editor_state(indoc! {"
23381        case \"$1\" in
23382            start)
23383                echo \"foo bar\"
23384                ;;
23385            *)ˇ
23386    "});
23387
23388    // test `fi` outdents to correct level with nested if blocks
23389    cx.set_state(indoc! {"
23390        if [ \"$1\" = \"test\" ]; then
23391            echo \"outer if\"
23392            if [ \"$2\" = \"debug\" ]; then
23393                echo \"inner if\"
23394                ˇ
23395    "});
23396    cx.update_editor(|editor, window, cx| {
23397        editor.handle_input("fi", window, cx);
23398    });
23399    cx.assert_editor_state(indoc! {"
23400        if [ \"$1\" = \"test\" ]; then
23401            echo \"outer if\"
23402            if [ \"$2\" = \"debug\" ]; then
23403                echo \"inner if\"
23404            fiˇ
23405    "});
23406}
23407
23408#[gpui::test]
23409async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
23410    init_test(cx, |_| {});
23411    update_test_language_settings(cx, |settings| {
23412        settings.defaults.extend_comment_on_newline = Some(false);
23413    });
23414    let mut cx = EditorTestContext::new(cx).await;
23415    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
23416    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23417
23418    // test correct indent after newline on comment
23419    cx.set_state(indoc! {"
23420        # COMMENT:ˇ
23421    "});
23422    cx.update_editor(|editor, window, cx| {
23423        editor.newline(&Newline, window, cx);
23424    });
23425    cx.assert_editor_state(indoc! {"
23426        # COMMENT:
23427        ˇ
23428    "});
23429
23430    // test correct indent after newline after `then`
23431    cx.set_state(indoc! {"
23432
23433        if [ \"$1\" = \"test\" ]; thenˇ
23434    "});
23435    cx.update_editor(|editor, window, cx| {
23436        editor.newline(&Newline, window, cx);
23437    });
23438    cx.run_until_parked();
23439    cx.assert_editor_state(indoc! {"
23440
23441        if [ \"$1\" = \"test\" ]; then
23442            ˇ
23443    "});
23444
23445    // test correct indent after newline after `else`
23446    cx.set_state(indoc! {"
23447        if [ \"$1\" = \"test\" ]; then
23448        elseˇ
23449    "});
23450    cx.update_editor(|editor, window, cx| {
23451        editor.newline(&Newline, window, cx);
23452    });
23453    cx.run_until_parked();
23454    cx.assert_editor_state(indoc! {"
23455        if [ \"$1\" = \"test\" ]; then
23456        else
23457            ˇ
23458    "});
23459
23460    // test correct indent after newline after `elif`
23461    cx.set_state(indoc! {"
23462        if [ \"$1\" = \"test\" ]; then
23463        elifˇ
23464    "});
23465    cx.update_editor(|editor, window, cx| {
23466        editor.newline(&Newline, window, cx);
23467    });
23468    cx.run_until_parked();
23469    cx.assert_editor_state(indoc! {"
23470        if [ \"$1\" = \"test\" ]; then
23471        elif
23472            ˇ
23473    "});
23474
23475    // test correct indent after newline after `do`
23476    cx.set_state(indoc! {"
23477        for file in *.txt; doˇ
23478    "});
23479    cx.update_editor(|editor, window, cx| {
23480        editor.newline(&Newline, window, cx);
23481    });
23482    cx.run_until_parked();
23483    cx.assert_editor_state(indoc! {"
23484        for file in *.txt; do
23485            ˇ
23486    "});
23487
23488    // test correct indent after newline after case pattern
23489    cx.set_state(indoc! {"
23490        case \"$1\" in
23491            start)ˇ
23492    "});
23493    cx.update_editor(|editor, window, cx| {
23494        editor.newline(&Newline, window, cx);
23495    });
23496    cx.run_until_parked();
23497    cx.assert_editor_state(indoc! {"
23498        case \"$1\" in
23499            start)
23500                ˇ
23501    "});
23502
23503    // test correct indent after newline after case pattern
23504    cx.set_state(indoc! {"
23505        case \"$1\" in
23506            start)
23507                ;;
23508            *)ˇ
23509    "});
23510    cx.update_editor(|editor, window, cx| {
23511        editor.newline(&Newline, window, cx);
23512    });
23513    cx.run_until_parked();
23514    cx.assert_editor_state(indoc! {"
23515        case \"$1\" in
23516            start)
23517                ;;
23518            *)
23519                ˇ
23520    "});
23521
23522    // test correct indent after newline after function opening brace
23523    cx.set_state(indoc! {"
23524        function test() {ˇ}
23525    "});
23526    cx.update_editor(|editor, window, cx| {
23527        editor.newline(&Newline, window, cx);
23528    });
23529    cx.run_until_parked();
23530    cx.assert_editor_state(indoc! {"
23531        function test() {
23532            ˇ
23533        }
23534    "});
23535
23536    // test no extra indent after semicolon on same line
23537    cx.set_state(indoc! {"
23538        echo \"test\"23539    "});
23540    cx.update_editor(|editor, window, cx| {
23541        editor.newline(&Newline, window, cx);
23542    });
23543    cx.run_until_parked();
23544    cx.assert_editor_state(indoc! {"
23545        echo \"test\";
23546        ˇ
23547    "});
23548}
23549
23550fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
23551    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
23552    point..point
23553}
23554
23555#[track_caller]
23556fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
23557    let (text, ranges) = marked_text_ranges(marked_text, true);
23558    assert_eq!(editor.text(cx), text);
23559    assert_eq!(
23560        editor.selections.ranges(cx),
23561        ranges,
23562        "Assert selections are {}",
23563        marked_text
23564    );
23565}
23566
23567pub fn handle_signature_help_request(
23568    cx: &mut EditorLspTestContext,
23569    mocked_response: lsp::SignatureHelp,
23570) -> impl Future<Output = ()> + use<> {
23571    let mut request =
23572        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
23573            let mocked_response = mocked_response.clone();
23574            async move { Ok(Some(mocked_response)) }
23575        });
23576
23577    async move {
23578        request.next().await;
23579    }
23580}
23581
23582#[track_caller]
23583pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
23584    cx.update_editor(|editor, _, _| {
23585        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
23586            let entries = menu.entries.borrow();
23587            let entries = entries
23588                .iter()
23589                .map(|entry| entry.string.as_str())
23590                .collect::<Vec<_>>();
23591            assert_eq!(entries, expected);
23592        } else {
23593            panic!("Expected completions menu");
23594        }
23595    });
23596}
23597
23598/// Handle completion request passing a marked string specifying where the completion
23599/// should be triggered from using '|' character, what range should be replaced, and what completions
23600/// should be returned using '<' and '>' to delimit the range.
23601///
23602/// Also see `handle_completion_request_with_insert_and_replace`.
23603#[track_caller]
23604pub fn handle_completion_request(
23605    marked_string: &str,
23606    completions: Vec<&'static str>,
23607    is_incomplete: bool,
23608    counter: Arc<AtomicUsize>,
23609    cx: &mut EditorLspTestContext,
23610) -> impl Future<Output = ()> {
23611    let complete_from_marker: TextRangeMarker = '|'.into();
23612    let replace_range_marker: TextRangeMarker = ('<', '>').into();
23613    let (_, mut marked_ranges) = marked_text_ranges_by(
23614        marked_string,
23615        vec![complete_from_marker.clone(), replace_range_marker.clone()],
23616    );
23617
23618    let complete_from_position =
23619        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
23620    let replace_range =
23621        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
23622
23623    let mut request =
23624        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
23625            let completions = completions.clone();
23626            counter.fetch_add(1, atomic::Ordering::Release);
23627            async move {
23628                assert_eq!(params.text_document_position.text_document.uri, url.clone());
23629                assert_eq!(
23630                    params.text_document_position.position,
23631                    complete_from_position
23632                );
23633                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
23634                    is_incomplete: is_incomplete,
23635                    item_defaults: None,
23636                    items: completions
23637                        .iter()
23638                        .map(|completion_text| lsp::CompletionItem {
23639                            label: completion_text.to_string(),
23640                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
23641                                range: replace_range,
23642                                new_text: completion_text.to_string(),
23643                            })),
23644                            ..Default::default()
23645                        })
23646                        .collect(),
23647                })))
23648            }
23649        });
23650
23651    async move {
23652        request.next().await;
23653    }
23654}
23655
23656/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
23657/// given instead, which also contains an `insert` range.
23658///
23659/// This function uses markers to define ranges:
23660/// - `|` marks the cursor position
23661/// - `<>` marks the replace range
23662/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
23663pub fn handle_completion_request_with_insert_and_replace(
23664    cx: &mut EditorLspTestContext,
23665    marked_string: &str,
23666    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
23667    counter: Arc<AtomicUsize>,
23668) -> impl Future<Output = ()> {
23669    let complete_from_marker: TextRangeMarker = '|'.into();
23670    let replace_range_marker: TextRangeMarker = ('<', '>').into();
23671    let insert_range_marker: TextRangeMarker = ('{', '}').into();
23672
23673    let (_, mut marked_ranges) = marked_text_ranges_by(
23674        marked_string,
23675        vec![
23676            complete_from_marker.clone(),
23677            replace_range_marker.clone(),
23678            insert_range_marker.clone(),
23679        ],
23680    );
23681
23682    let complete_from_position =
23683        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
23684    let replace_range =
23685        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
23686
23687    let insert_range = match marked_ranges.remove(&insert_range_marker) {
23688        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
23689        _ => lsp::Range {
23690            start: replace_range.start,
23691            end: complete_from_position,
23692        },
23693    };
23694
23695    let mut request =
23696        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
23697            let completions = completions.clone();
23698            counter.fetch_add(1, atomic::Ordering::Release);
23699            async move {
23700                assert_eq!(params.text_document_position.text_document.uri, url.clone());
23701                assert_eq!(
23702                    params.text_document_position.position, complete_from_position,
23703                    "marker `|` position doesn't match",
23704                );
23705                Ok(Some(lsp::CompletionResponse::Array(
23706                    completions
23707                        .iter()
23708                        .map(|(label, new_text)| lsp::CompletionItem {
23709                            label: label.to_string(),
23710                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23711                                lsp::InsertReplaceEdit {
23712                                    insert: insert_range,
23713                                    replace: replace_range,
23714                                    new_text: new_text.to_string(),
23715                                },
23716                            )),
23717                            ..Default::default()
23718                        })
23719                        .collect(),
23720                )))
23721            }
23722        });
23723
23724    async move {
23725        request.next().await;
23726    }
23727}
23728
23729fn handle_resolve_completion_request(
23730    cx: &mut EditorLspTestContext,
23731    edits: Option<Vec<(&'static str, &'static str)>>,
23732) -> impl Future<Output = ()> {
23733    let edits = edits.map(|edits| {
23734        edits
23735            .iter()
23736            .map(|(marked_string, new_text)| {
23737                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
23738                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
23739                lsp::TextEdit::new(replace_range, new_text.to_string())
23740            })
23741            .collect::<Vec<_>>()
23742    });
23743
23744    let mut request =
23745        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
23746            let edits = edits.clone();
23747            async move {
23748                Ok(lsp::CompletionItem {
23749                    additional_text_edits: edits,
23750                    ..Default::default()
23751                })
23752            }
23753        });
23754
23755    async move {
23756        request.next().await;
23757    }
23758}
23759
23760pub(crate) fn update_test_language_settings(
23761    cx: &mut TestAppContext,
23762    f: impl Fn(&mut AllLanguageSettingsContent),
23763) {
23764    cx.update(|cx| {
23765        SettingsStore::update_global(cx, |store, cx| {
23766            store.update_user_settings::<AllLanguageSettings>(cx, f);
23767        });
23768    });
23769}
23770
23771pub(crate) fn update_test_project_settings(
23772    cx: &mut TestAppContext,
23773    f: impl Fn(&mut ProjectSettings),
23774) {
23775    cx.update(|cx| {
23776        SettingsStore::update_global(cx, |store, cx| {
23777            store.update_user_settings::<ProjectSettings>(cx, f);
23778        });
23779    });
23780}
23781
23782pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
23783    cx.update(|cx| {
23784        assets::Assets.load_test_fonts(cx);
23785        let store = SettingsStore::test(cx);
23786        cx.set_global(store);
23787        theme::init(theme::LoadThemes::JustBase, cx);
23788        release_channel::init(SemanticVersion::default(), cx);
23789        client::init_settings(cx);
23790        language::init(cx);
23791        Project::init_settings(cx);
23792        workspace::init_settings(cx);
23793        crate::init(cx);
23794    });
23795    zlog::init_test();
23796    update_test_language_settings(cx, f);
23797}
23798
23799#[track_caller]
23800fn assert_hunk_revert(
23801    not_reverted_text_with_selections: &str,
23802    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
23803    expected_reverted_text_with_selections: &str,
23804    base_text: &str,
23805    cx: &mut EditorLspTestContext,
23806) {
23807    cx.set_state(not_reverted_text_with_selections);
23808    cx.set_head_text(base_text);
23809    cx.executor().run_until_parked();
23810
23811    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
23812        let snapshot = editor.snapshot(window, cx);
23813        let reverted_hunk_statuses = snapshot
23814            .buffer_snapshot
23815            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
23816            .map(|hunk| hunk.status().kind)
23817            .collect::<Vec<_>>();
23818
23819        editor.git_restore(&Default::default(), window, cx);
23820        reverted_hunk_statuses
23821    });
23822    cx.executor().run_until_parked();
23823    cx.assert_editor_state(expected_reverted_text_with_selections);
23824    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
23825}
23826
23827#[gpui::test(iterations = 10)]
23828async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
23829    init_test(cx, |_| {});
23830
23831    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
23832    let counter = diagnostic_requests.clone();
23833
23834    let fs = FakeFs::new(cx.executor());
23835    fs.insert_tree(
23836        path!("/a"),
23837        json!({
23838            "first.rs": "fn main() { let a = 5; }",
23839            "second.rs": "// Test file",
23840        }),
23841    )
23842    .await;
23843
23844    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23845    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23846    let cx = &mut VisualTestContext::from_window(*workspace, cx);
23847
23848    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23849    language_registry.add(rust_lang());
23850    let mut fake_servers = language_registry.register_fake_lsp(
23851        "Rust",
23852        FakeLspAdapter {
23853            capabilities: lsp::ServerCapabilities {
23854                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
23855                    lsp::DiagnosticOptions {
23856                        identifier: None,
23857                        inter_file_dependencies: true,
23858                        workspace_diagnostics: true,
23859                        work_done_progress_options: Default::default(),
23860                    },
23861                )),
23862                ..Default::default()
23863            },
23864            ..Default::default()
23865        },
23866    );
23867
23868    let editor = workspace
23869        .update(cx, |workspace, window, cx| {
23870            workspace.open_abs_path(
23871                PathBuf::from(path!("/a/first.rs")),
23872                OpenOptions::default(),
23873                window,
23874                cx,
23875            )
23876        })
23877        .unwrap()
23878        .await
23879        .unwrap()
23880        .downcast::<Editor>()
23881        .unwrap();
23882    let fake_server = fake_servers.next().await.unwrap();
23883    let server_id = fake_server.server.server_id();
23884    let mut first_request = fake_server
23885        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
23886            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
23887            let result_id = Some(new_result_id.to_string());
23888            assert_eq!(
23889                params.text_document.uri,
23890                lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
23891            );
23892            async move {
23893                Ok(lsp::DocumentDiagnosticReportResult::Report(
23894                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
23895                        related_documents: None,
23896                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
23897                            items: Vec::new(),
23898                            result_id,
23899                        },
23900                    }),
23901                ))
23902            }
23903        });
23904
23905    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
23906        project.update(cx, |project, cx| {
23907            let buffer_id = editor
23908                .read(cx)
23909                .buffer()
23910                .read(cx)
23911                .as_singleton()
23912                .expect("created a singleton buffer")
23913                .read(cx)
23914                .remote_id();
23915            let buffer_result_id = project
23916                .lsp_store()
23917                .read(cx)
23918                .result_id(server_id, buffer_id, cx);
23919            assert_eq!(expected, buffer_result_id);
23920        });
23921    };
23922
23923    ensure_result_id(None, cx);
23924    cx.executor().advance_clock(Duration::from_millis(60));
23925    cx.executor().run_until_parked();
23926    assert_eq!(
23927        diagnostic_requests.load(atomic::Ordering::Acquire),
23928        1,
23929        "Opening file should trigger diagnostic request"
23930    );
23931    first_request
23932        .next()
23933        .await
23934        .expect("should have sent the first diagnostics pull request");
23935    ensure_result_id(Some("1".to_string()), cx);
23936
23937    // Editing should trigger diagnostics
23938    editor.update_in(cx, |editor, window, cx| {
23939        editor.handle_input("2", window, cx)
23940    });
23941    cx.executor().advance_clock(Duration::from_millis(60));
23942    cx.executor().run_until_parked();
23943    assert_eq!(
23944        diagnostic_requests.load(atomic::Ordering::Acquire),
23945        2,
23946        "Editing should trigger diagnostic request"
23947    );
23948    ensure_result_id(Some("2".to_string()), cx);
23949
23950    // Moving cursor should not trigger diagnostic request
23951    editor.update_in(cx, |editor, window, cx| {
23952        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23953            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
23954        });
23955    });
23956    cx.executor().advance_clock(Duration::from_millis(60));
23957    cx.executor().run_until_parked();
23958    assert_eq!(
23959        diagnostic_requests.load(atomic::Ordering::Acquire),
23960        2,
23961        "Cursor movement should not trigger diagnostic request"
23962    );
23963    ensure_result_id(Some("2".to_string()), cx);
23964    // Multiple rapid edits should be debounced
23965    for _ in 0..5 {
23966        editor.update_in(cx, |editor, window, cx| {
23967            editor.handle_input("x", window, cx)
23968        });
23969    }
23970    cx.executor().advance_clock(Duration::from_millis(60));
23971    cx.executor().run_until_parked();
23972
23973    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
23974    assert!(
23975        final_requests <= 4,
23976        "Multiple rapid edits should be debounced (got {final_requests} requests)",
23977    );
23978    ensure_result_id(Some(final_requests.to_string()), cx);
23979}
23980
23981#[gpui::test]
23982async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
23983    // Regression test for issue #11671
23984    // Previously, adding a cursor after moving multiple cursors would reset
23985    // the cursor count instead of adding to the existing cursors.
23986    init_test(cx, |_| {});
23987    let mut cx = EditorTestContext::new(cx).await;
23988
23989    // Create a simple buffer with cursor at start
23990    cx.set_state(indoc! {"
23991        ˇaaaa
23992        bbbb
23993        cccc
23994        dddd
23995        eeee
23996        ffff
23997        gggg
23998        hhhh"});
23999
24000    // Add 2 cursors below (so we have 3 total)
24001    cx.update_editor(|editor, window, cx| {
24002        editor.add_selection_below(&Default::default(), window, cx);
24003        editor.add_selection_below(&Default::default(), window, cx);
24004    });
24005
24006    // Verify we have 3 cursors
24007    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
24008    assert_eq!(
24009        initial_count, 3,
24010        "Should have 3 cursors after adding 2 below"
24011    );
24012
24013    // Move down one line
24014    cx.update_editor(|editor, window, cx| {
24015        editor.move_down(&MoveDown, window, cx);
24016    });
24017
24018    // Add another cursor below
24019    cx.update_editor(|editor, window, cx| {
24020        editor.add_selection_below(&Default::default(), window, cx);
24021    });
24022
24023    // Should now have 4 cursors (3 original + 1 new)
24024    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
24025    assert_eq!(
24026        final_count, 4,
24027        "Should have 4 cursors after moving and adding another"
24028    );
24029}
24030
24031#[gpui::test(iterations = 10)]
24032async fn test_document_colors(cx: &mut TestAppContext) {
24033    let expected_color = Rgba {
24034        r: 0.33,
24035        g: 0.33,
24036        b: 0.33,
24037        a: 0.33,
24038    };
24039
24040    init_test(cx, |_| {});
24041
24042    let fs = FakeFs::new(cx.executor());
24043    fs.insert_tree(
24044        path!("/a"),
24045        json!({
24046            "first.rs": "fn main() { let a = 5; }",
24047        }),
24048    )
24049    .await;
24050
24051    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24052    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24053    let cx = &mut VisualTestContext::from_window(*workspace, cx);
24054
24055    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24056    language_registry.add(rust_lang());
24057    let mut fake_servers = language_registry.register_fake_lsp(
24058        "Rust",
24059        FakeLspAdapter {
24060            capabilities: lsp::ServerCapabilities {
24061                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
24062                ..lsp::ServerCapabilities::default()
24063            },
24064            name: "rust-analyzer",
24065            ..FakeLspAdapter::default()
24066        },
24067    );
24068    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
24069        "Rust",
24070        FakeLspAdapter {
24071            capabilities: lsp::ServerCapabilities {
24072                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
24073                ..lsp::ServerCapabilities::default()
24074            },
24075            name: "not-rust-analyzer",
24076            ..FakeLspAdapter::default()
24077        },
24078    );
24079
24080    let editor = workspace
24081        .update(cx, |workspace, window, cx| {
24082            workspace.open_abs_path(
24083                PathBuf::from(path!("/a/first.rs")),
24084                OpenOptions::default(),
24085                window,
24086                cx,
24087            )
24088        })
24089        .unwrap()
24090        .await
24091        .unwrap()
24092        .downcast::<Editor>()
24093        .unwrap();
24094    let fake_language_server = fake_servers.next().await.unwrap();
24095    let fake_language_server_without_capabilities =
24096        fake_servers_without_capabilities.next().await.unwrap();
24097    let requests_made = Arc::new(AtomicUsize::new(0));
24098    let closure_requests_made = Arc::clone(&requests_made);
24099    let mut color_request_handle = fake_language_server
24100        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
24101            let requests_made = Arc::clone(&closure_requests_made);
24102            async move {
24103                assert_eq!(
24104                    params.text_document.uri,
24105                    lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
24106                );
24107                requests_made.fetch_add(1, atomic::Ordering::Release);
24108                Ok(vec![
24109                    lsp::ColorInformation {
24110                        range: lsp::Range {
24111                            start: lsp::Position {
24112                                line: 0,
24113                                character: 0,
24114                            },
24115                            end: lsp::Position {
24116                                line: 0,
24117                                character: 1,
24118                            },
24119                        },
24120                        color: lsp::Color {
24121                            red: 0.33,
24122                            green: 0.33,
24123                            blue: 0.33,
24124                            alpha: 0.33,
24125                        },
24126                    },
24127                    lsp::ColorInformation {
24128                        range: lsp::Range {
24129                            start: lsp::Position {
24130                                line: 0,
24131                                character: 0,
24132                            },
24133                            end: lsp::Position {
24134                                line: 0,
24135                                character: 1,
24136                            },
24137                        },
24138                        color: lsp::Color {
24139                            red: 0.33,
24140                            green: 0.33,
24141                            blue: 0.33,
24142                            alpha: 0.33,
24143                        },
24144                    },
24145                ])
24146            }
24147        });
24148
24149    let _handle = fake_language_server_without_capabilities
24150        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
24151            panic!("Should not be called");
24152        });
24153    cx.executor().advance_clock(Duration::from_millis(100));
24154    color_request_handle.next().await.unwrap();
24155    cx.run_until_parked();
24156    assert_eq!(
24157        1,
24158        requests_made.load(atomic::Ordering::Acquire),
24159        "Should query for colors once per editor open"
24160    );
24161    editor.update_in(cx, |editor, _, cx| {
24162        assert_eq!(
24163            vec![expected_color],
24164            extract_color_inlays(editor, cx),
24165            "Should have an initial inlay"
24166        );
24167    });
24168
24169    // opening another file in a split should not influence the LSP query counter
24170    workspace
24171        .update(cx, |workspace, window, cx| {
24172            assert_eq!(
24173                workspace.panes().len(),
24174                1,
24175                "Should have one pane with one editor"
24176            );
24177            workspace.move_item_to_pane_in_direction(
24178                &MoveItemToPaneInDirection {
24179                    direction: SplitDirection::Right,
24180                    focus: false,
24181                    clone: true,
24182                },
24183                window,
24184                cx,
24185            );
24186        })
24187        .unwrap();
24188    cx.run_until_parked();
24189    workspace
24190        .update(cx, |workspace, _, cx| {
24191            let panes = workspace.panes();
24192            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
24193            for pane in panes {
24194                let editor = pane
24195                    .read(cx)
24196                    .active_item()
24197                    .and_then(|item| item.downcast::<Editor>())
24198                    .expect("Should have opened an editor in each split");
24199                let editor_file = editor
24200                    .read(cx)
24201                    .buffer()
24202                    .read(cx)
24203                    .as_singleton()
24204                    .expect("test deals with singleton buffers")
24205                    .read(cx)
24206                    .file()
24207                    .expect("test buffese should have a file")
24208                    .path();
24209                assert_eq!(
24210                    editor_file.as_ref(),
24211                    Path::new("first.rs"),
24212                    "Both editors should be opened for the same file"
24213                )
24214            }
24215        })
24216        .unwrap();
24217
24218    cx.executor().advance_clock(Duration::from_millis(500));
24219    let save = editor.update_in(cx, |editor, window, cx| {
24220        editor.move_to_end(&MoveToEnd, window, cx);
24221        editor.handle_input("dirty", window, cx);
24222        editor.save(
24223            SaveOptions {
24224                format: true,
24225                autosave: true,
24226            },
24227            project.clone(),
24228            window,
24229            cx,
24230        )
24231    });
24232    save.await.unwrap();
24233
24234    color_request_handle.next().await.unwrap();
24235    cx.run_until_parked();
24236    assert_eq!(
24237        3,
24238        requests_made.load(atomic::Ordering::Acquire),
24239        "Should query for colors once per save and once per formatting after save"
24240    );
24241
24242    drop(editor);
24243    let close = workspace
24244        .update(cx, |workspace, window, cx| {
24245            workspace.active_pane().update(cx, |pane, cx| {
24246                pane.close_active_item(&CloseActiveItem::default(), window, cx)
24247            })
24248        })
24249        .unwrap();
24250    close.await.unwrap();
24251    let close = workspace
24252        .update(cx, |workspace, window, cx| {
24253            workspace.active_pane().update(cx, |pane, cx| {
24254                pane.close_active_item(&CloseActiveItem::default(), window, cx)
24255            })
24256        })
24257        .unwrap();
24258    close.await.unwrap();
24259    assert_eq!(
24260        3,
24261        requests_made.load(atomic::Ordering::Acquire),
24262        "After saving and closing all editors, no extra requests should be made"
24263    );
24264    workspace
24265        .update(cx, |workspace, _, cx| {
24266            assert!(
24267                workspace.active_item(cx).is_none(),
24268                "Should close all editors"
24269            )
24270        })
24271        .unwrap();
24272
24273    workspace
24274        .update(cx, |workspace, window, cx| {
24275            workspace.active_pane().update(cx, |pane, cx| {
24276                pane.navigate_backward(window, cx);
24277            })
24278        })
24279        .unwrap();
24280    cx.executor().advance_clock(Duration::from_millis(100));
24281    cx.run_until_parked();
24282    let editor = workspace
24283        .update(cx, |workspace, _, cx| {
24284            workspace
24285                .active_item(cx)
24286                .expect("Should have reopened the editor again after navigating back")
24287                .downcast::<Editor>()
24288                .expect("Should be an editor")
24289        })
24290        .unwrap();
24291    color_request_handle.next().await.unwrap();
24292    assert_eq!(
24293        3,
24294        requests_made.load(atomic::Ordering::Acquire),
24295        "Cache should be reused on buffer close and reopen"
24296    );
24297    editor.update(cx, |editor, cx| {
24298        assert_eq!(
24299            vec![expected_color],
24300            extract_color_inlays(editor, cx),
24301            "Should have an initial inlay"
24302        );
24303    });
24304}
24305
24306#[gpui::test]
24307async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
24308    init_test(cx, |_| {});
24309    let (editor, cx) = cx.add_window_view(Editor::single_line);
24310    editor.update_in(cx, |editor, window, cx| {
24311        editor.set_text("oops\n\nwow\n", window, cx)
24312    });
24313    cx.run_until_parked();
24314    editor.update(cx, |editor, cx| {
24315        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
24316    });
24317    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
24318    cx.run_until_parked();
24319    editor.update(cx, |editor, cx| {
24320        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
24321    });
24322}
24323
24324#[track_caller]
24325fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
24326    editor
24327        .all_inlays(cx)
24328        .into_iter()
24329        .filter_map(|inlay| inlay.get_color())
24330        .map(Rgba::from)
24331        .collect()
24332}