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_nodes(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(indoc! { r#"use mod1::{mod2::{«mod3ˇ», mod4}, mod5::{mod6, «mod7ˇ»}};"# });
 8033    cx.update_editor(|editor, window, cx| {
 8034        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 8035    });
 8036
 8037    cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# });
 8038}
 8039
 8040#[gpui::test]
 8041async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 8042    init_test(cx, |_| {});
 8043
 8044    let base_text = r#"
 8045        impl A {
 8046            // this is an uncommitted comment
 8047
 8048            fn b() {
 8049                c();
 8050            }
 8051
 8052            // this is another uncommitted comment
 8053
 8054            fn d() {
 8055                // e
 8056                // f
 8057            }
 8058        }
 8059
 8060        fn g() {
 8061            // h
 8062        }
 8063    "#
 8064    .unindent();
 8065
 8066    let text = r#"
 8067        ˇimpl A {
 8068
 8069            fn b() {
 8070                c();
 8071            }
 8072
 8073            fn d() {
 8074                // e
 8075                // f
 8076            }
 8077        }
 8078
 8079        fn g() {
 8080            // h
 8081        }
 8082    "#
 8083    .unindent();
 8084
 8085    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 8086    cx.set_state(&text);
 8087    cx.set_head_text(&base_text);
 8088    cx.update_editor(|editor, window, cx| {
 8089        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 8090    });
 8091
 8092    cx.assert_state_with_diff(
 8093        "
 8094        ˇimpl A {
 8095      -     // this is an uncommitted comment
 8096
 8097            fn b() {
 8098                c();
 8099            }
 8100
 8101      -     // this is another uncommitted comment
 8102      -
 8103            fn d() {
 8104                // e
 8105                // f
 8106            }
 8107        }
 8108
 8109        fn g() {
 8110            // h
 8111        }
 8112    "
 8113        .unindent(),
 8114    );
 8115
 8116    let expected_display_text = "
 8117        impl A {
 8118            // this is an uncommitted comment
 8119
 8120            fn b() {
 8121 8122            }
 8123
 8124            // this is another uncommitted comment
 8125
 8126            fn d() {
 8127 8128            }
 8129        }
 8130
 8131        fn g() {
 8132 8133        }
 8134        "
 8135    .unindent();
 8136
 8137    cx.update_editor(|editor, window, cx| {
 8138        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 8139        assert_eq!(editor.display_text(cx), expected_display_text);
 8140    });
 8141}
 8142
 8143#[gpui::test]
 8144async fn test_autoindent(cx: &mut TestAppContext) {
 8145    init_test(cx, |_| {});
 8146
 8147    let language = Arc::new(
 8148        Language::new(
 8149            LanguageConfig {
 8150                brackets: BracketPairConfig {
 8151                    pairs: vec![
 8152                        BracketPair {
 8153                            start: "{".to_string(),
 8154                            end: "}".to_string(),
 8155                            close: false,
 8156                            surround: false,
 8157                            newline: true,
 8158                        },
 8159                        BracketPair {
 8160                            start: "(".to_string(),
 8161                            end: ")".to_string(),
 8162                            close: false,
 8163                            surround: false,
 8164                            newline: true,
 8165                        },
 8166                    ],
 8167                    ..Default::default()
 8168                },
 8169                ..Default::default()
 8170            },
 8171            Some(tree_sitter_rust::LANGUAGE.into()),
 8172        )
 8173        .with_indents_query(
 8174            r#"
 8175                (_ "(" ")" @end) @indent
 8176                (_ "{" "}" @end) @indent
 8177            "#,
 8178        )
 8179        .unwrap(),
 8180    );
 8181
 8182    let text = "fn a() {}";
 8183
 8184    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8185    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8186    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8187    editor
 8188        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8189        .await;
 8190
 8191    editor.update_in(cx, |editor, window, cx| {
 8192        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8193            s.select_ranges([5..5, 8..8, 9..9])
 8194        });
 8195        editor.newline(&Newline, window, cx);
 8196        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 8197        assert_eq!(
 8198            editor.selections.ranges(cx),
 8199            &[
 8200                Point::new(1, 4)..Point::new(1, 4),
 8201                Point::new(3, 4)..Point::new(3, 4),
 8202                Point::new(5, 0)..Point::new(5, 0)
 8203            ]
 8204        );
 8205    });
 8206}
 8207
 8208#[gpui::test]
 8209async fn test_autoindent_disabled(cx: &mut TestAppContext) {
 8210    init_test(cx, |settings| settings.defaults.auto_indent = Some(false));
 8211
 8212    let language = Arc::new(
 8213        Language::new(
 8214            LanguageConfig {
 8215                brackets: BracketPairConfig {
 8216                    pairs: vec![
 8217                        BracketPair {
 8218                            start: "{".to_string(),
 8219                            end: "}".to_string(),
 8220                            close: false,
 8221                            surround: false,
 8222                            newline: true,
 8223                        },
 8224                        BracketPair {
 8225                            start: "(".to_string(),
 8226                            end: ")".to_string(),
 8227                            close: false,
 8228                            surround: false,
 8229                            newline: true,
 8230                        },
 8231                    ],
 8232                    ..Default::default()
 8233                },
 8234                ..Default::default()
 8235            },
 8236            Some(tree_sitter_rust::LANGUAGE.into()),
 8237        )
 8238        .with_indents_query(
 8239            r#"
 8240                (_ "(" ")" @end) @indent
 8241                (_ "{" "}" @end) @indent
 8242            "#,
 8243        )
 8244        .unwrap(),
 8245    );
 8246
 8247    let text = "fn a() {}";
 8248
 8249    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8250    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8251    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8252    editor
 8253        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8254        .await;
 8255
 8256    editor.update_in(cx, |editor, window, cx| {
 8257        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8258            s.select_ranges([5..5, 8..8, 9..9])
 8259        });
 8260        editor.newline(&Newline, window, cx);
 8261        assert_eq!(
 8262            editor.text(cx),
 8263            indoc!(
 8264                "
 8265                fn a(
 8266
 8267                ) {
 8268
 8269                }
 8270                "
 8271            )
 8272        );
 8273        assert_eq!(
 8274            editor.selections.ranges(cx),
 8275            &[
 8276                Point::new(1, 0)..Point::new(1, 0),
 8277                Point::new(3, 0)..Point::new(3, 0),
 8278                Point::new(5, 0)..Point::new(5, 0)
 8279            ]
 8280        );
 8281    });
 8282}
 8283
 8284#[gpui::test]
 8285async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
 8286    init_test(cx, |settings| {
 8287        settings.defaults.auto_indent = Some(true);
 8288        settings.languages.0.insert(
 8289            "python".into(),
 8290            LanguageSettingsContent {
 8291                auto_indent: Some(false),
 8292                ..Default::default()
 8293            },
 8294        );
 8295    });
 8296
 8297    let mut cx = EditorTestContext::new(cx).await;
 8298
 8299    let injected_language = Arc::new(
 8300        Language::new(
 8301            LanguageConfig {
 8302                brackets: BracketPairConfig {
 8303                    pairs: vec![
 8304                        BracketPair {
 8305                            start: "{".to_string(),
 8306                            end: "}".to_string(),
 8307                            close: false,
 8308                            surround: false,
 8309                            newline: true,
 8310                        },
 8311                        BracketPair {
 8312                            start: "(".to_string(),
 8313                            end: ")".to_string(),
 8314                            close: true,
 8315                            surround: false,
 8316                            newline: true,
 8317                        },
 8318                    ],
 8319                    ..Default::default()
 8320                },
 8321                name: "python".into(),
 8322                ..Default::default()
 8323            },
 8324            Some(tree_sitter_python::LANGUAGE.into()),
 8325        )
 8326        .with_indents_query(
 8327            r#"
 8328                (_ "(" ")" @end) @indent
 8329                (_ "{" "}" @end) @indent
 8330            "#,
 8331        )
 8332        .unwrap(),
 8333    );
 8334
 8335    let language = Arc::new(
 8336        Language::new(
 8337            LanguageConfig {
 8338                brackets: BracketPairConfig {
 8339                    pairs: vec![
 8340                        BracketPair {
 8341                            start: "{".to_string(),
 8342                            end: "}".to_string(),
 8343                            close: false,
 8344                            surround: false,
 8345                            newline: true,
 8346                        },
 8347                        BracketPair {
 8348                            start: "(".to_string(),
 8349                            end: ")".to_string(),
 8350                            close: true,
 8351                            surround: false,
 8352                            newline: true,
 8353                        },
 8354                    ],
 8355                    ..Default::default()
 8356                },
 8357                name: LanguageName::new("rust"),
 8358                ..Default::default()
 8359            },
 8360            Some(tree_sitter_rust::LANGUAGE.into()),
 8361        )
 8362        .with_indents_query(
 8363            r#"
 8364                (_ "(" ")" @end) @indent
 8365                (_ "{" "}" @end) @indent
 8366            "#,
 8367        )
 8368        .unwrap()
 8369        .with_injection_query(
 8370            r#"
 8371            (macro_invocation
 8372                macro: (identifier) @_macro_name
 8373                (token_tree) @injection.content
 8374                (#set! injection.language "python"))
 8375           "#,
 8376        )
 8377        .unwrap(),
 8378    );
 8379
 8380    cx.language_registry().add(injected_language);
 8381    cx.language_registry().add(language.clone());
 8382
 8383    cx.update_buffer(|buffer, cx| {
 8384        buffer.set_language(Some(language), cx);
 8385    });
 8386
 8387    cx.set_state(r#"struct A {ˇ}"#);
 8388
 8389    cx.update_editor(|editor, window, cx| {
 8390        editor.newline(&Default::default(), window, cx);
 8391    });
 8392
 8393    cx.assert_editor_state(indoc!(
 8394        "struct A {
 8395            ˇ
 8396        }"
 8397    ));
 8398
 8399    cx.set_state(r#"select_biased!(ˇ)"#);
 8400
 8401    cx.update_editor(|editor, window, cx| {
 8402        editor.newline(&Default::default(), window, cx);
 8403        editor.handle_input("def ", window, cx);
 8404        editor.handle_input("(", window, cx);
 8405        editor.newline(&Default::default(), window, cx);
 8406        editor.handle_input("a", window, cx);
 8407    });
 8408
 8409    cx.assert_editor_state(indoc!(
 8410        "select_biased!(
 8411        def (
 8412 8413        )
 8414        )"
 8415    ));
 8416}
 8417
 8418#[gpui::test]
 8419async fn test_autoindent_selections(cx: &mut TestAppContext) {
 8420    init_test(cx, |_| {});
 8421
 8422    {
 8423        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 8424        cx.set_state(indoc! {"
 8425            impl A {
 8426
 8427                fn b() {}
 8428
 8429            «fn c() {
 8430
 8431            }ˇ»
 8432            }
 8433        "});
 8434
 8435        cx.update_editor(|editor, window, cx| {
 8436            editor.autoindent(&Default::default(), window, cx);
 8437        });
 8438
 8439        cx.assert_editor_state(indoc! {"
 8440            impl A {
 8441
 8442                fn b() {}
 8443
 8444                «fn c() {
 8445
 8446                }ˇ»
 8447            }
 8448        "});
 8449    }
 8450
 8451    {
 8452        let mut cx = EditorTestContext::new_multibuffer(
 8453            cx,
 8454            [indoc! { "
 8455                impl A {
 8456                «
 8457                // a
 8458                fn b(){}
 8459                »
 8460                «
 8461                    }
 8462                    fn c(){}
 8463                »
 8464            "}],
 8465        );
 8466
 8467        let buffer = cx.update_editor(|editor, _, cx| {
 8468            let buffer = editor.buffer().update(cx, |buffer, _| {
 8469                buffer.all_buffers().iter().next().unwrap().clone()
 8470            });
 8471            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 8472            buffer
 8473        });
 8474
 8475        cx.run_until_parked();
 8476        cx.update_editor(|editor, window, cx| {
 8477            editor.select_all(&Default::default(), window, cx);
 8478            editor.autoindent(&Default::default(), window, cx)
 8479        });
 8480        cx.run_until_parked();
 8481
 8482        cx.update(|_, cx| {
 8483            assert_eq!(
 8484                buffer.read(cx).text(),
 8485                indoc! { "
 8486                    impl A {
 8487
 8488                        // a
 8489                        fn b(){}
 8490
 8491
 8492                    }
 8493                    fn c(){}
 8494
 8495                " }
 8496            )
 8497        });
 8498    }
 8499}
 8500
 8501#[gpui::test]
 8502async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 8503    init_test(cx, |_| {});
 8504
 8505    let mut cx = EditorTestContext::new(cx).await;
 8506
 8507    let language = Arc::new(Language::new(
 8508        LanguageConfig {
 8509            brackets: BracketPairConfig {
 8510                pairs: vec![
 8511                    BracketPair {
 8512                        start: "{".to_string(),
 8513                        end: "}".to_string(),
 8514                        close: true,
 8515                        surround: true,
 8516                        newline: true,
 8517                    },
 8518                    BracketPair {
 8519                        start: "(".to_string(),
 8520                        end: ")".to_string(),
 8521                        close: true,
 8522                        surround: true,
 8523                        newline: true,
 8524                    },
 8525                    BracketPair {
 8526                        start: "/*".to_string(),
 8527                        end: " */".to_string(),
 8528                        close: true,
 8529                        surround: true,
 8530                        newline: true,
 8531                    },
 8532                    BracketPair {
 8533                        start: "[".to_string(),
 8534                        end: "]".to_string(),
 8535                        close: false,
 8536                        surround: false,
 8537                        newline: true,
 8538                    },
 8539                    BracketPair {
 8540                        start: "\"".to_string(),
 8541                        end: "\"".to_string(),
 8542                        close: true,
 8543                        surround: true,
 8544                        newline: false,
 8545                    },
 8546                    BracketPair {
 8547                        start: "<".to_string(),
 8548                        end: ">".to_string(),
 8549                        close: false,
 8550                        surround: true,
 8551                        newline: true,
 8552                    },
 8553                ],
 8554                ..Default::default()
 8555            },
 8556            autoclose_before: "})]".to_string(),
 8557            ..Default::default()
 8558        },
 8559        Some(tree_sitter_rust::LANGUAGE.into()),
 8560    ));
 8561
 8562    cx.language_registry().add(language.clone());
 8563    cx.update_buffer(|buffer, cx| {
 8564        buffer.set_language(Some(language), cx);
 8565    });
 8566
 8567    cx.set_state(
 8568        &r#"
 8569            🏀ˇ
 8570            εˇ
 8571            ❤️ˇ
 8572        "#
 8573        .unindent(),
 8574    );
 8575
 8576    // autoclose multiple nested brackets at multiple cursors
 8577    cx.update_editor(|editor, window, cx| {
 8578        editor.handle_input("{", window, cx);
 8579        editor.handle_input("{", window, cx);
 8580        editor.handle_input("{", window, cx);
 8581    });
 8582    cx.assert_editor_state(
 8583        &"
 8584            🏀{{{ˇ}}}
 8585            ε{{{ˇ}}}
 8586            ❤️{{{ˇ}}}
 8587        "
 8588        .unindent(),
 8589    );
 8590
 8591    // insert a different closing bracket
 8592    cx.update_editor(|editor, window, cx| {
 8593        editor.handle_input(")", window, cx);
 8594    });
 8595    cx.assert_editor_state(
 8596        &"
 8597            🏀{{{)ˇ}}}
 8598            ε{{{)ˇ}}}
 8599            ❤️{{{)ˇ}}}
 8600        "
 8601        .unindent(),
 8602    );
 8603
 8604    // skip over the auto-closed brackets when typing a closing bracket
 8605    cx.update_editor(|editor, window, cx| {
 8606        editor.move_right(&MoveRight, window, cx);
 8607        editor.handle_input("}", window, cx);
 8608        editor.handle_input("}", window, cx);
 8609        editor.handle_input("}", window, cx);
 8610    });
 8611    cx.assert_editor_state(
 8612        &"
 8613            🏀{{{)}}}}ˇ
 8614            ε{{{)}}}}ˇ
 8615            ❤️{{{)}}}}ˇ
 8616        "
 8617        .unindent(),
 8618    );
 8619
 8620    // autoclose multi-character pairs
 8621    cx.set_state(
 8622        &"
 8623            ˇ
 8624            ˇ
 8625        "
 8626        .unindent(),
 8627    );
 8628    cx.update_editor(|editor, window, cx| {
 8629        editor.handle_input("/", window, cx);
 8630        editor.handle_input("*", window, cx);
 8631    });
 8632    cx.assert_editor_state(
 8633        &"
 8634            /*ˇ */
 8635            /*ˇ */
 8636        "
 8637        .unindent(),
 8638    );
 8639
 8640    // one cursor autocloses a multi-character pair, one cursor
 8641    // does not autoclose.
 8642    cx.set_state(
 8643        &"
 8644 8645            ˇ
 8646        "
 8647        .unindent(),
 8648    );
 8649    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 8650    cx.assert_editor_state(
 8651        &"
 8652            /*ˇ */
 8653 8654        "
 8655        .unindent(),
 8656    );
 8657
 8658    // Don't autoclose if the next character isn't whitespace and isn't
 8659    // listed in the language's "autoclose_before" section.
 8660    cx.set_state("ˇa b");
 8661    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8662    cx.assert_editor_state("{ˇa b");
 8663
 8664    // Don't autoclose if `close` is false for the bracket pair
 8665    cx.set_state("ˇ");
 8666    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 8667    cx.assert_editor_state("");
 8668
 8669    // Surround with brackets if text is selected
 8670    cx.set_state("«aˇ» b");
 8671    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8672    cx.assert_editor_state("{«aˇ»} b");
 8673
 8674    // Autoclose when not immediately after a word character
 8675    cx.set_state("a ˇ");
 8676    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8677    cx.assert_editor_state("a \"ˇ\"");
 8678
 8679    // Autoclose pair where the start and end characters are the same
 8680    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8681    cx.assert_editor_state("a \"\"ˇ");
 8682
 8683    // Don't autoclose when immediately after a word character
 8684    cx.set_state("");
 8685    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8686    cx.assert_editor_state("a\"ˇ");
 8687
 8688    // Do autoclose when after a non-word character
 8689    cx.set_state("");
 8690    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8691    cx.assert_editor_state("{\"ˇ\"");
 8692
 8693    // Non identical pairs autoclose regardless of preceding character
 8694    cx.set_state("");
 8695    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8696    cx.assert_editor_state("a{ˇ}");
 8697
 8698    // Don't autoclose pair if autoclose is disabled
 8699    cx.set_state("ˇ");
 8700    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 8701    cx.assert_editor_state("");
 8702
 8703    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 8704    cx.set_state("«aˇ» b");
 8705    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 8706    cx.assert_editor_state("<«aˇ»> b");
 8707}
 8708
 8709#[gpui::test]
 8710async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 8711    init_test(cx, |settings| {
 8712        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 8713    });
 8714
 8715    let mut cx = EditorTestContext::new(cx).await;
 8716
 8717    let language = Arc::new(Language::new(
 8718        LanguageConfig {
 8719            brackets: BracketPairConfig {
 8720                pairs: vec![
 8721                    BracketPair {
 8722                        start: "{".to_string(),
 8723                        end: "}".to_string(),
 8724                        close: true,
 8725                        surround: true,
 8726                        newline: true,
 8727                    },
 8728                    BracketPair {
 8729                        start: "(".to_string(),
 8730                        end: ")".to_string(),
 8731                        close: true,
 8732                        surround: true,
 8733                        newline: true,
 8734                    },
 8735                    BracketPair {
 8736                        start: "[".to_string(),
 8737                        end: "]".to_string(),
 8738                        close: false,
 8739                        surround: false,
 8740                        newline: true,
 8741                    },
 8742                ],
 8743                ..Default::default()
 8744            },
 8745            autoclose_before: "})]".to_string(),
 8746            ..Default::default()
 8747        },
 8748        Some(tree_sitter_rust::LANGUAGE.into()),
 8749    ));
 8750
 8751    cx.language_registry().add(language.clone());
 8752    cx.update_buffer(|buffer, cx| {
 8753        buffer.set_language(Some(language), cx);
 8754    });
 8755
 8756    cx.set_state(
 8757        &"
 8758            ˇ
 8759            ˇ
 8760            ˇ
 8761        "
 8762        .unindent(),
 8763    );
 8764
 8765    // ensure only matching closing brackets are skipped over
 8766    cx.update_editor(|editor, window, cx| {
 8767        editor.handle_input("}", window, cx);
 8768        editor.move_left(&MoveLeft, window, cx);
 8769        editor.handle_input(")", window, cx);
 8770        editor.move_left(&MoveLeft, window, cx);
 8771    });
 8772    cx.assert_editor_state(
 8773        &"
 8774            ˇ)}
 8775            ˇ)}
 8776            ˇ)}
 8777        "
 8778        .unindent(),
 8779    );
 8780
 8781    // skip-over closing brackets at multiple cursors
 8782    cx.update_editor(|editor, window, cx| {
 8783        editor.handle_input(")", window, cx);
 8784        editor.handle_input("}", window, cx);
 8785    });
 8786    cx.assert_editor_state(
 8787        &"
 8788            )}ˇ
 8789            )}ˇ
 8790            )}ˇ
 8791        "
 8792        .unindent(),
 8793    );
 8794
 8795    // ignore non-close brackets
 8796    cx.update_editor(|editor, window, cx| {
 8797        editor.handle_input("]", window, cx);
 8798        editor.move_left(&MoveLeft, window, cx);
 8799        editor.handle_input("]", window, cx);
 8800    });
 8801    cx.assert_editor_state(
 8802        &"
 8803            )}]ˇ]
 8804            )}]ˇ]
 8805            )}]ˇ]
 8806        "
 8807        .unindent(),
 8808    );
 8809}
 8810
 8811#[gpui::test]
 8812async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 8813    init_test(cx, |_| {});
 8814
 8815    let mut cx = EditorTestContext::new(cx).await;
 8816
 8817    let html_language = Arc::new(
 8818        Language::new(
 8819            LanguageConfig {
 8820                name: "HTML".into(),
 8821                brackets: BracketPairConfig {
 8822                    pairs: vec![
 8823                        BracketPair {
 8824                            start: "<".into(),
 8825                            end: ">".into(),
 8826                            close: true,
 8827                            ..Default::default()
 8828                        },
 8829                        BracketPair {
 8830                            start: "{".into(),
 8831                            end: "}".into(),
 8832                            close: true,
 8833                            ..Default::default()
 8834                        },
 8835                        BracketPair {
 8836                            start: "(".into(),
 8837                            end: ")".into(),
 8838                            close: true,
 8839                            ..Default::default()
 8840                        },
 8841                    ],
 8842                    ..Default::default()
 8843                },
 8844                autoclose_before: "})]>".into(),
 8845                ..Default::default()
 8846            },
 8847            Some(tree_sitter_html::LANGUAGE.into()),
 8848        )
 8849        .with_injection_query(
 8850            r#"
 8851            (script_element
 8852                (raw_text) @injection.content
 8853                (#set! injection.language "javascript"))
 8854            "#,
 8855        )
 8856        .unwrap(),
 8857    );
 8858
 8859    let javascript_language = Arc::new(Language::new(
 8860        LanguageConfig {
 8861            name: "JavaScript".into(),
 8862            brackets: BracketPairConfig {
 8863                pairs: vec![
 8864                    BracketPair {
 8865                        start: "/*".into(),
 8866                        end: " */".into(),
 8867                        close: true,
 8868                        ..Default::default()
 8869                    },
 8870                    BracketPair {
 8871                        start: "{".into(),
 8872                        end: "}".into(),
 8873                        close: true,
 8874                        ..Default::default()
 8875                    },
 8876                    BracketPair {
 8877                        start: "(".into(),
 8878                        end: ")".into(),
 8879                        close: true,
 8880                        ..Default::default()
 8881                    },
 8882                ],
 8883                ..Default::default()
 8884            },
 8885            autoclose_before: "})]>".into(),
 8886            ..Default::default()
 8887        },
 8888        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 8889    ));
 8890
 8891    cx.language_registry().add(html_language.clone());
 8892    cx.language_registry().add(javascript_language.clone());
 8893    cx.executor().run_until_parked();
 8894
 8895    cx.update_buffer(|buffer, cx| {
 8896        buffer.set_language(Some(html_language), cx);
 8897    });
 8898
 8899    cx.set_state(
 8900        &r#"
 8901            <body>ˇ
 8902                <script>
 8903                    var x = 1;ˇ
 8904                </script>
 8905            </body>ˇ
 8906        "#
 8907        .unindent(),
 8908    );
 8909
 8910    // Precondition: different languages are active at different locations.
 8911    cx.update_editor(|editor, window, cx| {
 8912        let snapshot = editor.snapshot(window, cx);
 8913        let cursors = editor.selections.ranges::<usize>(cx);
 8914        let languages = cursors
 8915            .iter()
 8916            .map(|c| snapshot.language_at(c.start).unwrap().name())
 8917            .collect::<Vec<_>>();
 8918        assert_eq!(
 8919            languages,
 8920            &["HTML".into(), "JavaScript".into(), "HTML".into()]
 8921        );
 8922    });
 8923
 8924    // Angle brackets autoclose in HTML, but not JavaScript.
 8925    cx.update_editor(|editor, window, cx| {
 8926        editor.handle_input("<", window, cx);
 8927        editor.handle_input("a", window, cx);
 8928    });
 8929    cx.assert_editor_state(
 8930        &r#"
 8931            <body><aˇ>
 8932                <script>
 8933                    var x = 1;<aˇ
 8934                </script>
 8935            </body><aˇ>
 8936        "#
 8937        .unindent(),
 8938    );
 8939
 8940    // Curly braces and parens autoclose in both HTML and JavaScript.
 8941    cx.update_editor(|editor, window, cx| {
 8942        editor.handle_input(" b=", window, cx);
 8943        editor.handle_input("{", window, cx);
 8944        editor.handle_input("c", window, cx);
 8945        editor.handle_input("(", window, cx);
 8946    });
 8947    cx.assert_editor_state(
 8948        &r#"
 8949            <body><a b={c(ˇ)}>
 8950                <script>
 8951                    var x = 1;<a b={c(ˇ)}
 8952                </script>
 8953            </body><a b={c(ˇ)}>
 8954        "#
 8955        .unindent(),
 8956    );
 8957
 8958    // Brackets that were already autoclosed are skipped.
 8959    cx.update_editor(|editor, window, cx| {
 8960        editor.handle_input(")", window, cx);
 8961        editor.handle_input("d", window, cx);
 8962        editor.handle_input("}", window, cx);
 8963    });
 8964    cx.assert_editor_state(
 8965        &r#"
 8966            <body><a b={c()d}ˇ>
 8967                <script>
 8968                    var x = 1;<a b={c()d}ˇ
 8969                </script>
 8970            </body><a b={c()d}ˇ>
 8971        "#
 8972        .unindent(),
 8973    );
 8974    cx.update_editor(|editor, window, cx| {
 8975        editor.handle_input(">", window, cx);
 8976    });
 8977    cx.assert_editor_state(
 8978        &r#"
 8979            <body><a b={c()d}>ˇ
 8980                <script>
 8981                    var x = 1;<a b={c()d}>ˇ
 8982                </script>
 8983            </body><a b={c()d}>ˇ
 8984        "#
 8985        .unindent(),
 8986    );
 8987
 8988    // Reset
 8989    cx.set_state(
 8990        &r#"
 8991            <body>ˇ
 8992                <script>
 8993                    var x = 1;ˇ
 8994                </script>
 8995            </body>ˇ
 8996        "#
 8997        .unindent(),
 8998    );
 8999
 9000    cx.update_editor(|editor, window, cx| {
 9001        editor.handle_input("<", window, cx);
 9002    });
 9003    cx.assert_editor_state(
 9004        &r#"
 9005            <body><ˇ>
 9006                <script>
 9007                    var x = 1;<ˇ
 9008                </script>
 9009            </body><ˇ>
 9010        "#
 9011        .unindent(),
 9012    );
 9013
 9014    // When backspacing, the closing angle brackets are removed.
 9015    cx.update_editor(|editor, window, cx| {
 9016        editor.backspace(&Backspace, window, cx);
 9017    });
 9018    cx.assert_editor_state(
 9019        &r#"
 9020            <body>ˇ
 9021                <script>
 9022                    var x = 1;ˇ
 9023                </script>
 9024            </body>ˇ
 9025        "#
 9026        .unindent(),
 9027    );
 9028
 9029    // Block comments autoclose in JavaScript, but not HTML.
 9030    cx.update_editor(|editor, window, cx| {
 9031        editor.handle_input("/", window, cx);
 9032        editor.handle_input("*", window, cx);
 9033    });
 9034    cx.assert_editor_state(
 9035        &r#"
 9036            <body>/*ˇ
 9037                <script>
 9038                    var x = 1;/*ˇ */
 9039                </script>
 9040            </body>/*ˇ
 9041        "#
 9042        .unindent(),
 9043    );
 9044}
 9045
 9046#[gpui::test]
 9047async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
 9048    init_test(cx, |_| {});
 9049
 9050    let mut cx = EditorTestContext::new(cx).await;
 9051
 9052    let rust_language = Arc::new(
 9053        Language::new(
 9054            LanguageConfig {
 9055                name: "Rust".into(),
 9056                brackets: serde_json::from_value(json!([
 9057                    { "start": "{", "end": "}", "close": true, "newline": true },
 9058                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
 9059                ]))
 9060                .unwrap(),
 9061                autoclose_before: "})]>".into(),
 9062                ..Default::default()
 9063            },
 9064            Some(tree_sitter_rust::LANGUAGE.into()),
 9065        )
 9066        .with_override_query("(string_literal) @string")
 9067        .unwrap(),
 9068    );
 9069
 9070    cx.language_registry().add(rust_language.clone());
 9071    cx.update_buffer(|buffer, cx| {
 9072        buffer.set_language(Some(rust_language), cx);
 9073    });
 9074
 9075    cx.set_state(
 9076        &r#"
 9077            let x = ˇ
 9078        "#
 9079        .unindent(),
 9080    );
 9081
 9082    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
 9083    cx.update_editor(|editor, window, cx| {
 9084        editor.handle_input("\"", window, cx);
 9085    });
 9086    cx.assert_editor_state(
 9087        &r#"
 9088            let x = "ˇ"
 9089        "#
 9090        .unindent(),
 9091    );
 9092
 9093    // Inserting another quotation mark. The cursor moves across the existing
 9094    // automatically-inserted quotation mark.
 9095    cx.update_editor(|editor, window, cx| {
 9096        editor.handle_input("\"", window, cx);
 9097    });
 9098    cx.assert_editor_state(
 9099        &r#"
 9100            let x = ""ˇ
 9101        "#
 9102        .unindent(),
 9103    );
 9104
 9105    // Reset
 9106    cx.set_state(
 9107        &r#"
 9108            let x = ˇ
 9109        "#
 9110        .unindent(),
 9111    );
 9112
 9113    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
 9114    cx.update_editor(|editor, window, cx| {
 9115        editor.handle_input("\"", window, cx);
 9116        editor.handle_input(" ", window, cx);
 9117        editor.move_left(&Default::default(), window, cx);
 9118        editor.handle_input("\\", window, cx);
 9119        editor.handle_input("\"", window, cx);
 9120    });
 9121    cx.assert_editor_state(
 9122        &r#"
 9123            let x = "\"ˇ "
 9124        "#
 9125        .unindent(),
 9126    );
 9127
 9128    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
 9129    // mark. Nothing is inserted.
 9130    cx.update_editor(|editor, window, cx| {
 9131        editor.move_right(&Default::default(), window, cx);
 9132        editor.handle_input("\"", window, cx);
 9133    });
 9134    cx.assert_editor_state(
 9135        &r#"
 9136            let x = "\" "ˇ
 9137        "#
 9138        .unindent(),
 9139    );
 9140}
 9141
 9142#[gpui::test]
 9143async fn test_surround_with_pair(cx: &mut TestAppContext) {
 9144    init_test(cx, |_| {});
 9145
 9146    let language = Arc::new(Language::new(
 9147        LanguageConfig {
 9148            brackets: BracketPairConfig {
 9149                pairs: vec![
 9150                    BracketPair {
 9151                        start: "{".to_string(),
 9152                        end: "}".to_string(),
 9153                        close: true,
 9154                        surround: true,
 9155                        newline: true,
 9156                    },
 9157                    BracketPair {
 9158                        start: "/* ".to_string(),
 9159                        end: "*/".to_string(),
 9160                        close: true,
 9161                        surround: true,
 9162                        ..Default::default()
 9163                    },
 9164                ],
 9165                ..Default::default()
 9166            },
 9167            ..Default::default()
 9168        },
 9169        Some(tree_sitter_rust::LANGUAGE.into()),
 9170    ));
 9171
 9172    let text = r#"
 9173        a
 9174        b
 9175        c
 9176    "#
 9177    .unindent();
 9178
 9179    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9180    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9181    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9182    editor
 9183        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9184        .await;
 9185
 9186    editor.update_in(cx, |editor, window, cx| {
 9187        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9188            s.select_display_ranges([
 9189                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 9190                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 9191                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
 9192            ])
 9193        });
 9194
 9195        editor.handle_input("{", window, cx);
 9196        editor.handle_input("{", window, cx);
 9197        editor.handle_input("{", window, cx);
 9198        assert_eq!(
 9199            editor.text(cx),
 9200            "
 9201                {{{a}}}
 9202                {{{b}}}
 9203                {{{c}}}
 9204            "
 9205            .unindent()
 9206        );
 9207        assert_eq!(
 9208            editor.selections.display_ranges(cx),
 9209            [
 9210                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
 9211                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
 9212                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
 9213            ]
 9214        );
 9215
 9216        editor.undo(&Undo, window, cx);
 9217        editor.undo(&Undo, window, cx);
 9218        editor.undo(&Undo, window, cx);
 9219        assert_eq!(
 9220            editor.text(cx),
 9221            "
 9222                a
 9223                b
 9224                c
 9225            "
 9226            .unindent()
 9227        );
 9228        assert_eq!(
 9229            editor.selections.display_ranges(cx),
 9230            [
 9231                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 9232                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 9233                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 9234            ]
 9235        );
 9236
 9237        // Ensure inserting the first character of a multi-byte bracket pair
 9238        // doesn't surround the selections with the bracket.
 9239        editor.handle_input("/", window, cx);
 9240        assert_eq!(
 9241            editor.text(cx),
 9242            "
 9243                /
 9244                /
 9245                /
 9246            "
 9247            .unindent()
 9248        );
 9249        assert_eq!(
 9250            editor.selections.display_ranges(cx),
 9251            [
 9252                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 9253                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 9254                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 9255            ]
 9256        );
 9257
 9258        editor.undo(&Undo, window, cx);
 9259        assert_eq!(
 9260            editor.text(cx),
 9261            "
 9262                a
 9263                b
 9264                c
 9265            "
 9266            .unindent()
 9267        );
 9268        assert_eq!(
 9269            editor.selections.display_ranges(cx),
 9270            [
 9271                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 9272                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 9273                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 9274            ]
 9275        );
 9276
 9277        // Ensure inserting the last character of a multi-byte bracket pair
 9278        // doesn't surround the selections with the bracket.
 9279        editor.handle_input("*", window, cx);
 9280        assert_eq!(
 9281            editor.text(cx),
 9282            "
 9283                *
 9284                *
 9285                *
 9286            "
 9287            .unindent()
 9288        );
 9289        assert_eq!(
 9290            editor.selections.display_ranges(cx),
 9291            [
 9292                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 9293                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 9294                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 9295            ]
 9296        );
 9297    });
 9298}
 9299
 9300#[gpui::test]
 9301async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
 9302    init_test(cx, |_| {});
 9303
 9304    let language = Arc::new(Language::new(
 9305        LanguageConfig {
 9306            brackets: BracketPairConfig {
 9307                pairs: vec![BracketPair {
 9308                    start: "{".to_string(),
 9309                    end: "}".to_string(),
 9310                    close: true,
 9311                    surround: true,
 9312                    newline: true,
 9313                }],
 9314                ..Default::default()
 9315            },
 9316            autoclose_before: "}".to_string(),
 9317            ..Default::default()
 9318        },
 9319        Some(tree_sitter_rust::LANGUAGE.into()),
 9320    ));
 9321
 9322    let text = r#"
 9323        a
 9324        b
 9325        c
 9326    "#
 9327    .unindent();
 9328
 9329    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9330    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9331    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9332    editor
 9333        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9334        .await;
 9335
 9336    editor.update_in(cx, |editor, window, cx| {
 9337        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9338            s.select_ranges([
 9339                Point::new(0, 1)..Point::new(0, 1),
 9340                Point::new(1, 1)..Point::new(1, 1),
 9341                Point::new(2, 1)..Point::new(2, 1),
 9342            ])
 9343        });
 9344
 9345        editor.handle_input("{", window, cx);
 9346        editor.handle_input("{", window, cx);
 9347        editor.handle_input("_", window, cx);
 9348        assert_eq!(
 9349            editor.text(cx),
 9350            "
 9351                a{{_}}
 9352                b{{_}}
 9353                c{{_}}
 9354            "
 9355            .unindent()
 9356        );
 9357        assert_eq!(
 9358            editor.selections.ranges::<Point>(cx),
 9359            [
 9360                Point::new(0, 4)..Point::new(0, 4),
 9361                Point::new(1, 4)..Point::new(1, 4),
 9362                Point::new(2, 4)..Point::new(2, 4)
 9363            ]
 9364        );
 9365
 9366        editor.backspace(&Default::default(), window, cx);
 9367        editor.backspace(&Default::default(), window, cx);
 9368        assert_eq!(
 9369            editor.text(cx),
 9370            "
 9371                a{}
 9372                b{}
 9373                c{}
 9374            "
 9375            .unindent()
 9376        );
 9377        assert_eq!(
 9378            editor.selections.ranges::<Point>(cx),
 9379            [
 9380                Point::new(0, 2)..Point::new(0, 2),
 9381                Point::new(1, 2)..Point::new(1, 2),
 9382                Point::new(2, 2)..Point::new(2, 2)
 9383            ]
 9384        );
 9385
 9386        editor.delete_to_previous_word_start(&Default::default(), window, cx);
 9387        assert_eq!(
 9388            editor.text(cx),
 9389            "
 9390                a
 9391                b
 9392                c
 9393            "
 9394            .unindent()
 9395        );
 9396        assert_eq!(
 9397            editor.selections.ranges::<Point>(cx),
 9398            [
 9399                Point::new(0, 1)..Point::new(0, 1),
 9400                Point::new(1, 1)..Point::new(1, 1),
 9401                Point::new(2, 1)..Point::new(2, 1)
 9402            ]
 9403        );
 9404    });
 9405}
 9406
 9407#[gpui::test]
 9408async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
 9409    init_test(cx, |settings| {
 9410        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 9411    });
 9412
 9413    let mut cx = EditorTestContext::new(cx).await;
 9414
 9415    let language = Arc::new(Language::new(
 9416        LanguageConfig {
 9417            brackets: BracketPairConfig {
 9418                pairs: vec![
 9419                    BracketPair {
 9420                        start: "{".to_string(),
 9421                        end: "}".to_string(),
 9422                        close: true,
 9423                        surround: true,
 9424                        newline: true,
 9425                    },
 9426                    BracketPair {
 9427                        start: "(".to_string(),
 9428                        end: ")".to_string(),
 9429                        close: true,
 9430                        surround: true,
 9431                        newline: true,
 9432                    },
 9433                    BracketPair {
 9434                        start: "[".to_string(),
 9435                        end: "]".to_string(),
 9436                        close: false,
 9437                        surround: true,
 9438                        newline: true,
 9439                    },
 9440                ],
 9441                ..Default::default()
 9442            },
 9443            autoclose_before: "})]".to_string(),
 9444            ..Default::default()
 9445        },
 9446        Some(tree_sitter_rust::LANGUAGE.into()),
 9447    ));
 9448
 9449    cx.language_registry().add(language.clone());
 9450    cx.update_buffer(|buffer, cx| {
 9451        buffer.set_language(Some(language), cx);
 9452    });
 9453
 9454    cx.set_state(
 9455        &"
 9456            {(ˇ)}
 9457            [[ˇ]]
 9458            {(ˇ)}
 9459        "
 9460        .unindent(),
 9461    );
 9462
 9463    cx.update_editor(|editor, window, cx| {
 9464        editor.backspace(&Default::default(), window, cx);
 9465        editor.backspace(&Default::default(), window, cx);
 9466    });
 9467
 9468    cx.assert_editor_state(
 9469        &"
 9470            ˇ
 9471            ˇ]]
 9472            ˇ
 9473        "
 9474        .unindent(),
 9475    );
 9476
 9477    cx.update_editor(|editor, window, cx| {
 9478        editor.handle_input("{", window, cx);
 9479        editor.handle_input("{", window, cx);
 9480        editor.move_right(&MoveRight, window, cx);
 9481        editor.move_right(&MoveRight, window, cx);
 9482        editor.move_left(&MoveLeft, window, cx);
 9483        editor.move_left(&MoveLeft, window, cx);
 9484        editor.backspace(&Default::default(), window, cx);
 9485    });
 9486
 9487    cx.assert_editor_state(
 9488        &"
 9489            {ˇ}
 9490            {ˇ}]]
 9491            {ˇ}
 9492        "
 9493        .unindent(),
 9494    );
 9495
 9496    cx.update_editor(|editor, window, cx| {
 9497        editor.backspace(&Default::default(), window, cx);
 9498    });
 9499
 9500    cx.assert_editor_state(
 9501        &"
 9502            ˇ
 9503            ˇ]]
 9504            ˇ
 9505        "
 9506        .unindent(),
 9507    );
 9508}
 9509
 9510#[gpui::test]
 9511async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
 9512    init_test(cx, |_| {});
 9513
 9514    let language = Arc::new(Language::new(
 9515        LanguageConfig::default(),
 9516        Some(tree_sitter_rust::LANGUAGE.into()),
 9517    ));
 9518
 9519    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
 9520    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9521    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9522    editor
 9523        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9524        .await;
 9525
 9526    editor.update_in(cx, |editor, window, cx| {
 9527        editor.set_auto_replace_emoji_shortcode(true);
 9528
 9529        editor.handle_input("Hello ", window, cx);
 9530        editor.handle_input(":wave", window, cx);
 9531        assert_eq!(editor.text(cx), "Hello :wave".unindent());
 9532
 9533        editor.handle_input(":", window, cx);
 9534        assert_eq!(editor.text(cx), "Hello 👋".unindent());
 9535
 9536        editor.handle_input(" :smile", window, cx);
 9537        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
 9538
 9539        editor.handle_input(":", window, cx);
 9540        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
 9541
 9542        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
 9543        editor.handle_input(":wave", window, cx);
 9544        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
 9545
 9546        editor.handle_input(":", window, cx);
 9547        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
 9548
 9549        editor.handle_input(":1", window, cx);
 9550        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
 9551
 9552        editor.handle_input(":", window, cx);
 9553        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
 9554
 9555        // Ensure shortcode does not get replaced when it is part of a word
 9556        editor.handle_input(" Test:wave", window, cx);
 9557        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
 9558
 9559        editor.handle_input(":", window, cx);
 9560        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
 9561
 9562        editor.set_auto_replace_emoji_shortcode(false);
 9563
 9564        // Ensure shortcode does not get replaced when auto replace is off
 9565        editor.handle_input(" :wave", window, cx);
 9566        assert_eq!(
 9567            editor.text(cx),
 9568            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
 9569        );
 9570
 9571        editor.handle_input(":", window, cx);
 9572        assert_eq!(
 9573            editor.text(cx),
 9574            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
 9575        );
 9576    });
 9577}
 9578
 9579#[gpui::test]
 9580async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
 9581    init_test(cx, |_| {});
 9582
 9583    let (text, insertion_ranges) = marked_text_ranges(
 9584        indoc! {"
 9585            ˇ
 9586        "},
 9587        false,
 9588    );
 9589
 9590    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
 9591    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9592
 9593    _ = editor.update_in(cx, |editor, window, cx| {
 9594        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
 9595
 9596        editor
 9597            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9598            .unwrap();
 9599
 9600        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
 9601            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
 9602            assert_eq!(editor.text(cx), expected_text);
 9603            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
 9604        }
 9605
 9606        assert(
 9607            editor,
 9608            cx,
 9609            indoc! {"
 9610            type «» =•
 9611            "},
 9612        );
 9613
 9614        assert!(editor.context_menu_visible(), "There should be a matches");
 9615    });
 9616}
 9617
 9618#[gpui::test]
 9619async fn test_snippets(cx: &mut TestAppContext) {
 9620    init_test(cx, |_| {});
 9621
 9622    let mut cx = EditorTestContext::new(cx).await;
 9623
 9624    cx.set_state(indoc! {"
 9625        a.ˇ b
 9626        a.ˇ b
 9627        a.ˇ b
 9628    "});
 9629
 9630    cx.update_editor(|editor, window, cx| {
 9631        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
 9632        let insertion_ranges = editor
 9633            .selections
 9634            .all(cx)
 9635            .iter()
 9636            .map(|s| s.range().clone())
 9637            .collect::<Vec<_>>();
 9638        editor
 9639            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9640            .unwrap();
 9641    });
 9642
 9643    cx.assert_editor_state(indoc! {"
 9644        a.f(«oneˇ», two, «threeˇ») b
 9645        a.f(«oneˇ», two, «threeˇ») b
 9646        a.f(«oneˇ», two, «threeˇ») b
 9647    "});
 9648
 9649    // Can't move earlier than the first tab stop
 9650    cx.update_editor(|editor, window, cx| {
 9651        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 9652    });
 9653    cx.assert_editor_state(indoc! {"
 9654        a.f(«oneˇ», two, «threeˇ») b
 9655        a.f(«oneˇ», two, «threeˇ») b
 9656        a.f(«oneˇ», two, «threeˇ») b
 9657    "});
 9658
 9659    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9660    cx.assert_editor_state(indoc! {"
 9661        a.f(one, «twoˇ», three) b
 9662        a.f(one, «twoˇ», three) b
 9663        a.f(one, «twoˇ», three) b
 9664    "});
 9665
 9666    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
 9667    cx.assert_editor_state(indoc! {"
 9668        a.f(«oneˇ», two, «threeˇ») b
 9669        a.f(«oneˇ», two, «threeˇ») b
 9670        a.f(«oneˇ», two, «threeˇ») b
 9671    "});
 9672
 9673    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9674    cx.assert_editor_state(indoc! {"
 9675        a.f(one, «twoˇ», three) b
 9676        a.f(one, «twoˇ», three) b
 9677        a.f(one, «twoˇ», three) b
 9678    "});
 9679    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9680    cx.assert_editor_state(indoc! {"
 9681        a.f(one, two, three)ˇ b
 9682        a.f(one, two, three)ˇ b
 9683        a.f(one, two, three)ˇ b
 9684    "});
 9685
 9686    // As soon as the last tab stop is reached, snippet state is gone
 9687    cx.update_editor(|editor, window, cx| {
 9688        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 9689    });
 9690    cx.assert_editor_state(indoc! {"
 9691        a.f(one, two, three)ˇ b
 9692        a.f(one, two, three)ˇ b
 9693        a.f(one, two, three)ˇ b
 9694    "});
 9695}
 9696
 9697#[gpui::test]
 9698async fn test_snippet_indentation(cx: &mut TestAppContext) {
 9699    init_test(cx, |_| {});
 9700
 9701    let mut cx = EditorTestContext::new(cx).await;
 9702
 9703    cx.update_editor(|editor, window, cx| {
 9704        let snippet = Snippet::parse(indoc! {"
 9705            /*
 9706             * Multiline comment with leading indentation
 9707             *
 9708             * $1
 9709             */
 9710            $0"})
 9711        .unwrap();
 9712        let insertion_ranges = editor
 9713            .selections
 9714            .all(cx)
 9715            .iter()
 9716            .map(|s| s.range().clone())
 9717            .collect::<Vec<_>>();
 9718        editor
 9719            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9720            .unwrap();
 9721    });
 9722
 9723    cx.assert_editor_state(indoc! {"
 9724        /*
 9725         * Multiline comment with leading indentation
 9726         *
 9727         * ˇ
 9728         */
 9729    "});
 9730
 9731    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9732    cx.assert_editor_state(indoc! {"
 9733        /*
 9734         * Multiline comment with leading indentation
 9735         *
 9736         *•
 9737         */
 9738        ˇ"});
 9739}
 9740
 9741#[gpui::test]
 9742async fn test_document_format_during_save(cx: &mut TestAppContext) {
 9743    init_test(cx, |_| {});
 9744
 9745    let fs = FakeFs::new(cx.executor());
 9746    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9747
 9748    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
 9749
 9750    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9751    language_registry.add(rust_lang());
 9752    let mut fake_servers = language_registry.register_fake_lsp(
 9753        "Rust",
 9754        FakeLspAdapter {
 9755            capabilities: lsp::ServerCapabilities {
 9756                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9757                ..Default::default()
 9758            },
 9759            ..Default::default()
 9760        },
 9761    );
 9762
 9763    let buffer = project
 9764        .update(cx, |project, cx| {
 9765            project.open_local_buffer(path!("/file.rs"), cx)
 9766        })
 9767        .await
 9768        .unwrap();
 9769
 9770    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9771    let (editor, cx) = cx.add_window_view(|window, cx| {
 9772        build_editor_with_project(project.clone(), buffer, window, cx)
 9773    });
 9774    editor.update_in(cx, |editor, window, cx| {
 9775        editor.set_text("one\ntwo\nthree\n", window, cx)
 9776    });
 9777    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9778
 9779    cx.executor().start_waiting();
 9780    let fake_server = fake_servers.next().await.unwrap();
 9781
 9782    {
 9783        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9784            move |params, _| async move {
 9785                assert_eq!(
 9786                    params.text_document.uri,
 9787                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9788                );
 9789                assert_eq!(params.options.tab_size, 4);
 9790                Ok(Some(vec![lsp::TextEdit::new(
 9791                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9792                    ", ".to_string(),
 9793                )]))
 9794            },
 9795        );
 9796        let save = editor
 9797            .update_in(cx, |editor, window, cx| {
 9798                editor.save(
 9799                    SaveOptions {
 9800                        format: true,
 9801                        autosave: false,
 9802                    },
 9803                    project.clone(),
 9804                    window,
 9805                    cx,
 9806                )
 9807            })
 9808            .unwrap();
 9809        cx.executor().start_waiting();
 9810        save.await;
 9811
 9812        assert_eq!(
 9813            editor.update(cx, |editor, cx| editor.text(cx)),
 9814            "one, two\nthree\n"
 9815        );
 9816        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9817    }
 9818
 9819    {
 9820        editor.update_in(cx, |editor, window, cx| {
 9821            editor.set_text("one\ntwo\nthree\n", window, cx)
 9822        });
 9823        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9824
 9825        // Ensure we can still save even if formatting hangs.
 9826        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9827            move |params, _| async move {
 9828                assert_eq!(
 9829                    params.text_document.uri,
 9830                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9831                );
 9832                futures::future::pending::<()>().await;
 9833                unreachable!()
 9834            },
 9835        );
 9836        let save = editor
 9837            .update_in(cx, |editor, window, cx| {
 9838                editor.save(
 9839                    SaveOptions {
 9840                        format: true,
 9841                        autosave: false,
 9842                    },
 9843                    project.clone(),
 9844                    window,
 9845                    cx,
 9846                )
 9847            })
 9848            .unwrap();
 9849        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9850        cx.executor().start_waiting();
 9851        save.await;
 9852        assert_eq!(
 9853            editor.update(cx, |editor, cx| editor.text(cx)),
 9854            "one\ntwo\nthree\n"
 9855        );
 9856    }
 9857
 9858    // Set rust language override and assert overridden tabsize is sent to language server
 9859    update_test_language_settings(cx, |settings| {
 9860        settings.languages.0.insert(
 9861            "Rust".into(),
 9862            LanguageSettingsContent {
 9863                tab_size: NonZeroU32::new(8),
 9864                ..Default::default()
 9865            },
 9866        );
 9867    });
 9868
 9869    {
 9870        editor.update_in(cx, |editor, window, cx| {
 9871            editor.set_text("somehting_new\n", window, cx)
 9872        });
 9873        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9874        let _formatting_request_signal = fake_server
 9875            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9876                assert_eq!(
 9877                    params.text_document.uri,
 9878                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9879                );
 9880                assert_eq!(params.options.tab_size, 8);
 9881                Ok(Some(vec![]))
 9882            });
 9883        let save = editor
 9884            .update_in(cx, |editor, window, cx| {
 9885                editor.save(
 9886                    SaveOptions {
 9887                        format: true,
 9888                        autosave: false,
 9889                    },
 9890                    project.clone(),
 9891                    window,
 9892                    cx,
 9893                )
 9894            })
 9895            .unwrap();
 9896        cx.executor().start_waiting();
 9897        save.await;
 9898    }
 9899}
 9900
 9901#[gpui::test]
 9902async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
 9903    init_test(cx, |settings| {
 9904        settings.defaults.ensure_final_newline_on_save = Some(false);
 9905    });
 9906
 9907    let fs = FakeFs::new(cx.executor());
 9908    fs.insert_file(path!("/file.txt"), "foo".into()).await;
 9909
 9910    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
 9911
 9912    let buffer = project
 9913        .update(cx, |project, cx| {
 9914            project.open_local_buffer(path!("/file.txt"), cx)
 9915        })
 9916        .await
 9917        .unwrap();
 9918
 9919    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9920    let (editor, cx) = cx.add_window_view(|window, cx| {
 9921        build_editor_with_project(project.clone(), buffer, window, cx)
 9922    });
 9923    editor.update_in(cx, |editor, window, cx| {
 9924        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 9925            s.select_ranges([0..0])
 9926        });
 9927    });
 9928    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9929
 9930    editor.update_in(cx, |editor, window, cx| {
 9931        editor.handle_input("\n", window, cx)
 9932    });
 9933    cx.run_until_parked();
 9934    save(&editor, &project, cx).await;
 9935    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
 9936
 9937    editor.update_in(cx, |editor, window, cx| {
 9938        editor.undo(&Default::default(), window, cx);
 9939    });
 9940    save(&editor, &project, cx).await;
 9941    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
 9942
 9943    editor.update_in(cx, |editor, window, cx| {
 9944        editor.redo(&Default::default(), window, cx);
 9945    });
 9946    cx.run_until_parked();
 9947    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
 9948
 9949    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
 9950        let save = editor
 9951            .update_in(cx, |editor, window, cx| {
 9952                editor.save(
 9953                    SaveOptions {
 9954                        format: true,
 9955                        autosave: false,
 9956                    },
 9957                    project.clone(),
 9958                    window,
 9959                    cx,
 9960                )
 9961            })
 9962            .unwrap();
 9963        cx.executor().start_waiting();
 9964        save.await;
 9965        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9966    }
 9967}
 9968
 9969#[gpui::test]
 9970async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
 9971    init_test(cx, |_| {});
 9972
 9973    let cols = 4;
 9974    let rows = 10;
 9975    let sample_text_1 = sample_text(rows, cols, 'a');
 9976    assert_eq!(
 9977        sample_text_1,
 9978        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
 9979    );
 9980    let sample_text_2 = sample_text(rows, cols, 'l');
 9981    assert_eq!(
 9982        sample_text_2,
 9983        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
 9984    );
 9985    let sample_text_3 = sample_text(rows, cols, 'v');
 9986    assert_eq!(
 9987        sample_text_3,
 9988        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
 9989    );
 9990
 9991    let fs = FakeFs::new(cx.executor());
 9992    fs.insert_tree(
 9993        path!("/a"),
 9994        json!({
 9995            "main.rs": sample_text_1,
 9996            "other.rs": sample_text_2,
 9997            "lib.rs": sample_text_3,
 9998        }),
 9999    )
10000    .await;
10001
10002    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
10003    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
10004    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10005
10006    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10007    language_registry.add(rust_lang());
10008    let mut fake_servers = language_registry.register_fake_lsp(
10009        "Rust",
10010        FakeLspAdapter {
10011            capabilities: lsp::ServerCapabilities {
10012                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10013                ..Default::default()
10014            },
10015            ..Default::default()
10016        },
10017    );
10018
10019    let worktree = project.update(cx, |project, cx| {
10020        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
10021        assert_eq!(worktrees.len(), 1);
10022        worktrees.pop().unwrap()
10023    });
10024    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
10025
10026    let buffer_1 = project
10027        .update(cx, |project, cx| {
10028            project.open_buffer((worktree_id, "main.rs"), cx)
10029        })
10030        .await
10031        .unwrap();
10032    let buffer_2 = project
10033        .update(cx, |project, cx| {
10034            project.open_buffer((worktree_id, "other.rs"), cx)
10035        })
10036        .await
10037        .unwrap();
10038    let buffer_3 = project
10039        .update(cx, |project, cx| {
10040            project.open_buffer((worktree_id, "lib.rs"), cx)
10041        })
10042        .await
10043        .unwrap();
10044
10045    let multi_buffer = cx.new(|cx| {
10046        let mut multi_buffer = MultiBuffer::new(ReadWrite);
10047        multi_buffer.push_excerpts(
10048            buffer_1.clone(),
10049            [
10050                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
10051                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
10052                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
10053            ],
10054            cx,
10055        );
10056        multi_buffer.push_excerpts(
10057            buffer_2.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_3.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
10075    });
10076    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
10077        Editor::new(
10078            EditorMode::full(),
10079            multi_buffer,
10080            Some(project.clone()),
10081            window,
10082            cx,
10083        )
10084    });
10085
10086    multi_buffer_editor.update_in(cx, |editor, window, cx| {
10087        editor.change_selections(
10088            SelectionEffects::scroll(Autoscroll::Next),
10089            window,
10090            cx,
10091            |s| s.select_ranges(Some(1..2)),
10092        );
10093        editor.insert("|one|two|three|", window, cx);
10094    });
10095    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
10096    multi_buffer_editor.update_in(cx, |editor, window, cx| {
10097        editor.change_selections(
10098            SelectionEffects::scroll(Autoscroll::Next),
10099            window,
10100            cx,
10101            |s| s.select_ranges(Some(60..70)),
10102        );
10103        editor.insert("|four|five|six|", window, cx);
10104    });
10105    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
10106
10107    // First two buffers should be edited, but not the third one.
10108    assert_eq!(
10109        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
10110        "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}",
10111    );
10112    buffer_1.update(cx, |buffer, _| {
10113        assert!(buffer.is_dirty());
10114        assert_eq!(
10115            buffer.text(),
10116            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
10117        )
10118    });
10119    buffer_2.update(cx, |buffer, _| {
10120        assert!(buffer.is_dirty());
10121        assert_eq!(
10122            buffer.text(),
10123            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
10124        )
10125    });
10126    buffer_3.update(cx, |buffer, _| {
10127        assert!(!buffer.is_dirty());
10128        assert_eq!(buffer.text(), sample_text_3,)
10129    });
10130    cx.executor().run_until_parked();
10131
10132    cx.executor().start_waiting();
10133    let save = multi_buffer_editor
10134        .update_in(cx, |editor, window, cx| {
10135            editor.save(
10136                SaveOptions {
10137                    format: true,
10138                    autosave: false,
10139                },
10140                project.clone(),
10141                window,
10142                cx,
10143            )
10144        })
10145        .unwrap();
10146
10147    let fake_server = fake_servers.next().await.unwrap();
10148    fake_server
10149        .server
10150        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
10151            Ok(Some(vec![lsp::TextEdit::new(
10152                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10153                format!("[{} formatted]", params.text_document.uri),
10154            )]))
10155        })
10156        .detach();
10157    save.await;
10158
10159    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
10160    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
10161    assert_eq!(
10162        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
10163        uri!(
10164            "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}"
10165        ),
10166    );
10167    buffer_1.update(cx, |buffer, _| {
10168        assert!(!buffer.is_dirty());
10169        assert_eq!(
10170            buffer.text(),
10171            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
10172        )
10173    });
10174    buffer_2.update(cx, |buffer, _| {
10175        assert!(!buffer.is_dirty());
10176        assert_eq!(
10177            buffer.text(),
10178            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
10179        )
10180    });
10181    buffer_3.update(cx, |buffer, _| {
10182        assert!(!buffer.is_dirty());
10183        assert_eq!(buffer.text(), sample_text_3,)
10184    });
10185}
10186
10187#[gpui::test]
10188async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
10189    init_test(cx, |_| {});
10190
10191    let fs = FakeFs::new(cx.executor());
10192    fs.insert_tree(
10193        path!("/dir"),
10194        json!({
10195            "file1.rs": "fn main() { println!(\"hello\"); }",
10196            "file2.rs": "fn test() { println!(\"test\"); }",
10197            "file3.rs": "fn other() { println!(\"other\"); }\n",
10198        }),
10199    )
10200    .await;
10201
10202    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
10203    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
10204    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10205
10206    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10207    language_registry.add(rust_lang());
10208
10209    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
10210    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
10211
10212    // Open three buffers
10213    let buffer_1 = project
10214        .update(cx, |project, cx| {
10215            project.open_buffer((worktree_id, "file1.rs"), cx)
10216        })
10217        .await
10218        .unwrap();
10219    let buffer_2 = project
10220        .update(cx, |project, cx| {
10221            project.open_buffer((worktree_id, "file2.rs"), cx)
10222        })
10223        .await
10224        .unwrap();
10225    let buffer_3 = project
10226        .update(cx, |project, cx| {
10227            project.open_buffer((worktree_id, "file3.rs"), cx)
10228        })
10229        .await
10230        .unwrap();
10231
10232    // Create a multi-buffer with all three buffers
10233    let multi_buffer = cx.new(|cx| {
10234        let mut multi_buffer = MultiBuffer::new(ReadWrite);
10235        multi_buffer.push_excerpts(
10236            buffer_1.clone(),
10237            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
10238            cx,
10239        );
10240        multi_buffer.push_excerpts(
10241            buffer_2.clone(),
10242            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
10243            cx,
10244        );
10245        multi_buffer.push_excerpts(
10246            buffer_3.clone(),
10247            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
10248            cx,
10249        );
10250        multi_buffer
10251    });
10252
10253    let editor = cx.new_window_entity(|window, cx| {
10254        Editor::new(
10255            EditorMode::full(),
10256            multi_buffer,
10257            Some(project.clone()),
10258            window,
10259            cx,
10260        )
10261    });
10262
10263    // Edit only the first buffer
10264    editor.update_in(cx, |editor, window, cx| {
10265        editor.change_selections(
10266            SelectionEffects::scroll(Autoscroll::Next),
10267            window,
10268            cx,
10269            |s| s.select_ranges(Some(10..10)),
10270        );
10271        editor.insert("// edited", window, cx);
10272    });
10273
10274    // Verify that only buffer 1 is dirty
10275    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
10276    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10277    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10278
10279    // Get write counts after file creation (files were created with initial content)
10280    // We expect each file to have been written once during creation
10281    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
10282    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
10283    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
10284
10285    // Perform autosave
10286    let save_task = editor.update_in(cx, |editor, window, cx| {
10287        editor.save(
10288            SaveOptions {
10289                format: true,
10290                autosave: true,
10291            },
10292            project.clone(),
10293            window,
10294            cx,
10295        )
10296    });
10297    save_task.await.unwrap();
10298
10299    // Only the dirty buffer should have been saved
10300    assert_eq!(
10301        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
10302        1,
10303        "Buffer 1 was dirty, so it should have been written once during autosave"
10304    );
10305    assert_eq!(
10306        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
10307        0,
10308        "Buffer 2 was clean, so it should not have been written during autosave"
10309    );
10310    assert_eq!(
10311        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
10312        0,
10313        "Buffer 3 was clean, so it should not have been written during autosave"
10314    );
10315
10316    // Verify buffer states after autosave
10317    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10318    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10319    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10320
10321    // Now perform a manual save (format = true)
10322    let save_task = editor.update_in(cx, |editor, window, cx| {
10323        editor.save(
10324            SaveOptions {
10325                format: true,
10326                autosave: false,
10327            },
10328            project.clone(),
10329            window,
10330            cx,
10331        )
10332    });
10333    save_task.await.unwrap();
10334
10335    // During manual save, clean buffers don't get written to disk
10336    // They just get did_save called for language server notifications
10337    assert_eq!(
10338        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
10339        1,
10340        "Buffer 1 should only have been written once total (during autosave, not manual save)"
10341    );
10342    assert_eq!(
10343        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
10344        0,
10345        "Buffer 2 should not have been written at all"
10346    );
10347    assert_eq!(
10348        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
10349        0,
10350        "Buffer 3 should not have been written at all"
10351    );
10352}
10353
10354async fn setup_range_format_test(
10355    cx: &mut TestAppContext,
10356) -> (
10357    Entity<Project>,
10358    Entity<Editor>,
10359    &mut gpui::VisualTestContext,
10360    lsp::FakeLanguageServer,
10361) {
10362    init_test(cx, |_| {});
10363
10364    let fs = FakeFs::new(cx.executor());
10365    fs.insert_file(path!("/file.rs"), Default::default()).await;
10366
10367    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10368
10369    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10370    language_registry.add(rust_lang());
10371    let mut fake_servers = language_registry.register_fake_lsp(
10372        "Rust",
10373        FakeLspAdapter {
10374            capabilities: lsp::ServerCapabilities {
10375                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
10376                ..lsp::ServerCapabilities::default()
10377            },
10378            ..FakeLspAdapter::default()
10379        },
10380    );
10381
10382    let buffer = project
10383        .update(cx, |project, cx| {
10384            project.open_local_buffer(path!("/file.rs"), cx)
10385        })
10386        .await
10387        .unwrap();
10388
10389    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10390    let (editor, cx) = cx.add_window_view(|window, cx| {
10391        build_editor_with_project(project.clone(), buffer, window, cx)
10392    });
10393
10394    cx.executor().start_waiting();
10395    let fake_server = fake_servers.next().await.unwrap();
10396
10397    (project, editor, cx, fake_server)
10398}
10399
10400#[gpui::test]
10401async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
10402    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
10403
10404    editor.update_in(cx, |editor, window, cx| {
10405        editor.set_text("one\ntwo\nthree\n", window, cx)
10406    });
10407    assert!(cx.read(|cx| editor.is_dirty(cx)));
10408
10409    let save = editor
10410        .update_in(cx, |editor, window, cx| {
10411            editor.save(
10412                SaveOptions {
10413                    format: true,
10414                    autosave: false,
10415                },
10416                project.clone(),
10417                window,
10418                cx,
10419            )
10420        })
10421        .unwrap();
10422    fake_server
10423        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
10424            assert_eq!(
10425                params.text_document.uri,
10426                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10427            );
10428            assert_eq!(params.options.tab_size, 4);
10429            Ok(Some(vec![lsp::TextEdit::new(
10430                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10431                ", ".to_string(),
10432            )]))
10433        })
10434        .next()
10435        .await;
10436    cx.executor().start_waiting();
10437    save.await;
10438    assert_eq!(
10439        editor.update(cx, |editor, cx| editor.text(cx)),
10440        "one, two\nthree\n"
10441    );
10442    assert!(!cx.read(|cx| editor.is_dirty(cx)));
10443}
10444
10445#[gpui::test]
10446async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
10447    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
10448
10449    editor.update_in(cx, |editor, window, cx| {
10450        editor.set_text("one\ntwo\nthree\n", window, cx)
10451    });
10452    assert!(cx.read(|cx| editor.is_dirty(cx)));
10453
10454    // Test that save still works when formatting hangs
10455    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
10456        move |params, _| async move {
10457            assert_eq!(
10458                params.text_document.uri,
10459                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10460            );
10461            futures::future::pending::<()>().await;
10462            unreachable!()
10463        },
10464    );
10465    let save = editor
10466        .update_in(cx, |editor, window, cx| {
10467            editor.save(
10468                SaveOptions {
10469                    format: true,
10470                    autosave: false,
10471                },
10472                project.clone(),
10473                window,
10474                cx,
10475            )
10476        })
10477        .unwrap();
10478    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10479    cx.executor().start_waiting();
10480    save.await;
10481    assert_eq!(
10482        editor.update(cx, |editor, cx| editor.text(cx)),
10483        "one\ntwo\nthree\n"
10484    );
10485    assert!(!cx.read(|cx| editor.is_dirty(cx)));
10486}
10487
10488#[gpui::test]
10489async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
10490    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
10491
10492    // Buffer starts clean, no formatting should be requested
10493    let save = editor
10494        .update_in(cx, |editor, window, cx| {
10495            editor.save(
10496                SaveOptions {
10497                    format: false,
10498                    autosave: false,
10499                },
10500                project.clone(),
10501                window,
10502                cx,
10503            )
10504        })
10505        .unwrap();
10506    let _pending_format_request = fake_server
10507        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
10508            panic!("Should not be invoked");
10509        })
10510        .next();
10511    cx.executor().start_waiting();
10512    save.await;
10513    cx.run_until_parked();
10514}
10515
10516#[gpui::test]
10517async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
10518    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
10519
10520    // Set Rust language override and assert overridden tabsize is sent to language server
10521    update_test_language_settings(cx, |settings| {
10522        settings.languages.0.insert(
10523            "Rust".into(),
10524            LanguageSettingsContent {
10525                tab_size: NonZeroU32::new(8),
10526                ..Default::default()
10527            },
10528        );
10529    });
10530
10531    editor.update_in(cx, |editor, window, cx| {
10532        editor.set_text("something_new\n", window, cx)
10533    });
10534    assert!(cx.read(|cx| editor.is_dirty(cx)));
10535    let save = editor
10536        .update_in(cx, |editor, window, cx| {
10537            editor.save(
10538                SaveOptions {
10539                    format: true,
10540                    autosave: false,
10541                },
10542                project.clone(),
10543                window,
10544                cx,
10545            )
10546        })
10547        .unwrap();
10548    fake_server
10549        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
10550            assert_eq!(
10551                params.text_document.uri,
10552                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10553            );
10554            assert_eq!(params.options.tab_size, 8);
10555            Ok(Some(Vec::new()))
10556        })
10557        .next()
10558        .await;
10559    save.await;
10560}
10561
10562#[gpui::test]
10563async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
10564    init_test(cx, |settings| {
10565        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
10566            Formatter::LanguageServer { name: None },
10567        )))
10568    });
10569
10570    let fs = FakeFs::new(cx.executor());
10571    fs.insert_file(path!("/file.rs"), Default::default()).await;
10572
10573    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10574
10575    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10576    language_registry.add(Arc::new(Language::new(
10577        LanguageConfig {
10578            name: "Rust".into(),
10579            matcher: LanguageMatcher {
10580                path_suffixes: vec!["rs".to_string()],
10581                ..Default::default()
10582            },
10583            ..LanguageConfig::default()
10584        },
10585        Some(tree_sitter_rust::LANGUAGE.into()),
10586    )));
10587    update_test_language_settings(cx, |settings| {
10588        // Enable Prettier formatting for the same buffer, and ensure
10589        // LSP is called instead of Prettier.
10590        settings.defaults.prettier = Some(PrettierSettings {
10591            allowed: true,
10592            ..PrettierSettings::default()
10593        });
10594    });
10595    let mut fake_servers = language_registry.register_fake_lsp(
10596        "Rust",
10597        FakeLspAdapter {
10598            capabilities: lsp::ServerCapabilities {
10599                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10600                ..Default::default()
10601            },
10602            ..Default::default()
10603        },
10604    );
10605
10606    let buffer = project
10607        .update(cx, |project, cx| {
10608            project.open_local_buffer(path!("/file.rs"), cx)
10609        })
10610        .await
10611        .unwrap();
10612
10613    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10614    let (editor, cx) = cx.add_window_view(|window, cx| {
10615        build_editor_with_project(project.clone(), buffer, window, cx)
10616    });
10617    editor.update_in(cx, |editor, window, cx| {
10618        editor.set_text("one\ntwo\nthree\n", window, cx)
10619    });
10620
10621    cx.executor().start_waiting();
10622    let fake_server = fake_servers.next().await.unwrap();
10623
10624    let format = editor
10625        .update_in(cx, |editor, window, cx| {
10626            editor.perform_format(
10627                project.clone(),
10628                FormatTrigger::Manual,
10629                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10630                window,
10631                cx,
10632            )
10633        })
10634        .unwrap();
10635    fake_server
10636        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
10637            assert_eq!(
10638                params.text_document.uri,
10639                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10640            );
10641            assert_eq!(params.options.tab_size, 4);
10642            Ok(Some(vec![lsp::TextEdit::new(
10643                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10644                ", ".to_string(),
10645            )]))
10646        })
10647        .next()
10648        .await;
10649    cx.executor().start_waiting();
10650    format.await;
10651    assert_eq!(
10652        editor.update(cx, |editor, cx| editor.text(cx)),
10653        "one, two\nthree\n"
10654    );
10655
10656    editor.update_in(cx, |editor, window, cx| {
10657        editor.set_text("one\ntwo\nthree\n", window, cx)
10658    });
10659    // Ensure we don't lock if formatting hangs.
10660    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10661        move |params, _| async move {
10662            assert_eq!(
10663                params.text_document.uri,
10664                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10665            );
10666            futures::future::pending::<()>().await;
10667            unreachable!()
10668        },
10669    );
10670    let format = editor
10671        .update_in(cx, |editor, window, cx| {
10672            editor.perform_format(
10673                project,
10674                FormatTrigger::Manual,
10675                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10676                window,
10677                cx,
10678            )
10679        })
10680        .unwrap();
10681    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10682    cx.executor().start_waiting();
10683    format.await;
10684    assert_eq!(
10685        editor.update(cx, |editor, cx| editor.text(cx)),
10686        "one\ntwo\nthree\n"
10687    );
10688}
10689
10690#[gpui::test]
10691async fn test_multiple_formatters(cx: &mut TestAppContext) {
10692    init_test(cx, |settings| {
10693        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
10694        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
10695            Formatter::LanguageServer { name: None },
10696            Formatter::CodeActions(
10697                [
10698                    ("code-action-1".into(), true),
10699                    ("code-action-2".into(), true),
10700                ]
10701                .into_iter()
10702                .collect(),
10703            ),
10704        ])))
10705    });
10706
10707    let fs = FakeFs::new(cx.executor());
10708    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
10709        .await;
10710
10711    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10712    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10713    language_registry.add(rust_lang());
10714
10715    let mut fake_servers = language_registry.register_fake_lsp(
10716        "Rust",
10717        FakeLspAdapter {
10718            capabilities: lsp::ServerCapabilities {
10719                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10720                execute_command_provider: Some(lsp::ExecuteCommandOptions {
10721                    commands: vec!["the-command-for-code-action-1".into()],
10722                    ..Default::default()
10723                }),
10724                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10725                ..Default::default()
10726            },
10727            ..Default::default()
10728        },
10729    );
10730
10731    let buffer = project
10732        .update(cx, |project, cx| {
10733            project.open_local_buffer(path!("/file.rs"), cx)
10734        })
10735        .await
10736        .unwrap();
10737
10738    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10739    let (editor, cx) = cx.add_window_view(|window, cx| {
10740        build_editor_with_project(project.clone(), buffer, window, cx)
10741    });
10742
10743    cx.executor().start_waiting();
10744
10745    let fake_server = fake_servers.next().await.unwrap();
10746    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10747        move |_params, _| async move {
10748            Ok(Some(vec![lsp::TextEdit::new(
10749                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10750                "applied-formatting\n".to_string(),
10751            )]))
10752        },
10753    );
10754    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10755        move |params, _| async move {
10756            assert_eq!(
10757                params.context.only,
10758                Some(vec!["code-action-1".into(), "code-action-2".into()])
10759            );
10760            let uri = lsp::Url::from_file_path(path!("/file.rs")).unwrap();
10761            Ok(Some(vec![
10762                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10763                    kind: Some("code-action-1".into()),
10764                    edit: Some(lsp::WorkspaceEdit::new(
10765                        [(
10766                            uri.clone(),
10767                            vec![lsp::TextEdit::new(
10768                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10769                                "applied-code-action-1-edit\n".to_string(),
10770                            )],
10771                        )]
10772                        .into_iter()
10773                        .collect(),
10774                    )),
10775                    command: Some(lsp::Command {
10776                        command: "the-command-for-code-action-1".into(),
10777                        ..Default::default()
10778                    }),
10779                    ..Default::default()
10780                }),
10781                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10782                    kind: Some("code-action-2".into()),
10783                    edit: Some(lsp::WorkspaceEdit::new(
10784                        [(
10785                            uri.clone(),
10786                            vec![lsp::TextEdit::new(
10787                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10788                                "applied-code-action-2-edit\n".to_string(),
10789                            )],
10790                        )]
10791                        .into_iter()
10792                        .collect(),
10793                    )),
10794                    ..Default::default()
10795                }),
10796            ]))
10797        },
10798    );
10799
10800    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
10801        move |params, _| async move { Ok(params) }
10802    });
10803
10804    let command_lock = Arc::new(futures::lock::Mutex::new(()));
10805    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
10806        let fake = fake_server.clone();
10807        let lock = command_lock.clone();
10808        move |params, _| {
10809            assert_eq!(params.command, "the-command-for-code-action-1");
10810            let fake = fake.clone();
10811            let lock = lock.clone();
10812            async move {
10813                lock.lock().await;
10814                fake.server
10815                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
10816                        label: None,
10817                        edit: lsp::WorkspaceEdit {
10818                            changes: Some(
10819                                [(
10820                                    lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
10821                                    vec![lsp::TextEdit {
10822                                        range: lsp::Range::new(
10823                                            lsp::Position::new(0, 0),
10824                                            lsp::Position::new(0, 0),
10825                                        ),
10826                                        new_text: "applied-code-action-1-command\n".into(),
10827                                    }],
10828                                )]
10829                                .into_iter()
10830                                .collect(),
10831                            ),
10832                            ..Default::default()
10833                        },
10834                    })
10835                    .await
10836                    .into_response()
10837                    .unwrap();
10838                Ok(Some(json!(null)))
10839            }
10840        }
10841    });
10842
10843    cx.executor().start_waiting();
10844    editor
10845        .update_in(cx, |editor, window, cx| {
10846            editor.perform_format(
10847                project.clone(),
10848                FormatTrigger::Manual,
10849                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10850                window,
10851                cx,
10852            )
10853        })
10854        .unwrap()
10855        .await;
10856    editor.update(cx, |editor, cx| {
10857        assert_eq!(
10858            editor.text(cx),
10859            r#"
10860                applied-code-action-2-edit
10861                applied-code-action-1-command
10862                applied-code-action-1-edit
10863                applied-formatting
10864                one
10865                two
10866                three
10867            "#
10868            .unindent()
10869        );
10870    });
10871
10872    editor.update_in(cx, |editor, window, cx| {
10873        editor.undo(&Default::default(), window, cx);
10874        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10875    });
10876
10877    // Perform a manual edit while waiting for an LSP command
10878    // that's being run as part of a formatting code action.
10879    let lock_guard = command_lock.lock().await;
10880    let format = editor
10881        .update_in(cx, |editor, window, cx| {
10882            editor.perform_format(
10883                project.clone(),
10884                FormatTrigger::Manual,
10885                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10886                window,
10887                cx,
10888            )
10889        })
10890        .unwrap();
10891    cx.run_until_parked();
10892    editor.update(cx, |editor, cx| {
10893        assert_eq!(
10894            editor.text(cx),
10895            r#"
10896                applied-code-action-1-edit
10897                applied-formatting
10898                one
10899                two
10900                three
10901            "#
10902            .unindent()
10903        );
10904
10905        editor.buffer.update(cx, |buffer, cx| {
10906            let ix = buffer.len(cx);
10907            buffer.edit([(ix..ix, "edited\n")], None, cx);
10908        });
10909    });
10910
10911    // Allow the LSP command to proceed. Because the buffer was edited,
10912    // the second code action will not be run.
10913    drop(lock_guard);
10914    format.await;
10915    editor.update_in(cx, |editor, window, cx| {
10916        assert_eq!(
10917            editor.text(cx),
10918            r#"
10919                applied-code-action-1-command
10920                applied-code-action-1-edit
10921                applied-formatting
10922                one
10923                two
10924                three
10925                edited
10926            "#
10927            .unindent()
10928        );
10929
10930        // The manual edit is undone first, because it is the last thing the user did
10931        // (even though the command completed afterwards).
10932        editor.undo(&Default::default(), window, cx);
10933        assert_eq!(
10934            editor.text(cx),
10935            r#"
10936                applied-code-action-1-command
10937                applied-code-action-1-edit
10938                applied-formatting
10939                one
10940                two
10941                three
10942            "#
10943            .unindent()
10944        );
10945
10946        // All the formatting (including the command, which completed after the manual edit)
10947        // is undone together.
10948        editor.undo(&Default::default(), window, cx);
10949        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10950    });
10951}
10952
10953#[gpui::test]
10954async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
10955    init_test(cx, |settings| {
10956        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
10957            Formatter::LanguageServer { name: None },
10958        ])))
10959    });
10960
10961    let fs = FakeFs::new(cx.executor());
10962    fs.insert_file(path!("/file.ts"), Default::default()).await;
10963
10964    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10965
10966    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10967    language_registry.add(Arc::new(Language::new(
10968        LanguageConfig {
10969            name: "TypeScript".into(),
10970            matcher: LanguageMatcher {
10971                path_suffixes: vec!["ts".to_string()],
10972                ..Default::default()
10973            },
10974            ..LanguageConfig::default()
10975        },
10976        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
10977    )));
10978    update_test_language_settings(cx, |settings| {
10979        settings.defaults.prettier = Some(PrettierSettings {
10980            allowed: true,
10981            ..PrettierSettings::default()
10982        });
10983    });
10984    let mut fake_servers = language_registry.register_fake_lsp(
10985        "TypeScript",
10986        FakeLspAdapter {
10987            capabilities: lsp::ServerCapabilities {
10988                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10989                ..Default::default()
10990            },
10991            ..Default::default()
10992        },
10993    );
10994
10995    let buffer = project
10996        .update(cx, |project, cx| {
10997            project.open_local_buffer(path!("/file.ts"), cx)
10998        })
10999        .await
11000        .unwrap();
11001
11002    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11003    let (editor, cx) = cx.add_window_view(|window, cx| {
11004        build_editor_with_project(project.clone(), buffer, window, cx)
11005    });
11006    editor.update_in(cx, |editor, window, cx| {
11007        editor.set_text(
11008            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
11009            window,
11010            cx,
11011        )
11012    });
11013
11014    cx.executor().start_waiting();
11015    let fake_server = fake_servers.next().await.unwrap();
11016
11017    let format = editor
11018        .update_in(cx, |editor, window, cx| {
11019            editor.perform_code_action_kind(
11020                project.clone(),
11021                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
11022                window,
11023                cx,
11024            )
11025        })
11026        .unwrap();
11027    fake_server
11028        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
11029            assert_eq!(
11030                params.text_document.uri,
11031                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
11032            );
11033            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
11034                lsp::CodeAction {
11035                    title: "Organize Imports".to_string(),
11036                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
11037                    edit: Some(lsp::WorkspaceEdit {
11038                        changes: Some(
11039                            [(
11040                                params.text_document.uri.clone(),
11041                                vec![lsp::TextEdit::new(
11042                                    lsp::Range::new(
11043                                        lsp::Position::new(1, 0),
11044                                        lsp::Position::new(2, 0),
11045                                    ),
11046                                    "".to_string(),
11047                                )],
11048                            )]
11049                            .into_iter()
11050                            .collect(),
11051                        ),
11052                        ..Default::default()
11053                    }),
11054                    ..Default::default()
11055                },
11056            )]))
11057        })
11058        .next()
11059        .await;
11060    cx.executor().start_waiting();
11061    format.await;
11062    assert_eq!(
11063        editor.update(cx, |editor, cx| editor.text(cx)),
11064        "import { a } from 'module';\n\nconst x = a;\n"
11065    );
11066
11067    editor.update_in(cx, |editor, window, cx| {
11068        editor.set_text(
11069            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
11070            window,
11071            cx,
11072        )
11073    });
11074    // Ensure we don't lock if code action hangs.
11075    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
11076        move |params, _| async move {
11077            assert_eq!(
11078                params.text_document.uri,
11079                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
11080            );
11081            futures::future::pending::<()>().await;
11082            unreachable!()
11083        },
11084    );
11085    let format = editor
11086        .update_in(cx, |editor, window, cx| {
11087            editor.perform_code_action_kind(
11088                project,
11089                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
11090                window,
11091                cx,
11092            )
11093        })
11094        .unwrap();
11095    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
11096    cx.executor().start_waiting();
11097    format.await;
11098    assert_eq!(
11099        editor.update(cx, |editor, cx| editor.text(cx)),
11100        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
11101    );
11102}
11103
11104#[gpui::test]
11105async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
11106    init_test(cx, |_| {});
11107
11108    let mut cx = EditorLspTestContext::new_rust(
11109        lsp::ServerCapabilities {
11110            document_formatting_provider: Some(lsp::OneOf::Left(true)),
11111            ..Default::default()
11112        },
11113        cx,
11114    )
11115    .await;
11116
11117    cx.set_state(indoc! {"
11118        one.twoˇ
11119    "});
11120
11121    // The format request takes a long time. When it completes, it inserts
11122    // a newline and an indent before the `.`
11123    cx.lsp
11124        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
11125            let executor = cx.background_executor().clone();
11126            async move {
11127                executor.timer(Duration::from_millis(100)).await;
11128                Ok(Some(vec![lsp::TextEdit {
11129                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
11130                    new_text: "\n    ".into(),
11131                }]))
11132            }
11133        });
11134
11135    // Submit a format request.
11136    let format_1 = cx
11137        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
11138        .unwrap();
11139    cx.executor().run_until_parked();
11140
11141    // Submit a second format request.
11142    let format_2 = cx
11143        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
11144        .unwrap();
11145    cx.executor().run_until_parked();
11146
11147    // Wait for both format requests to complete
11148    cx.executor().advance_clock(Duration::from_millis(200));
11149    cx.executor().start_waiting();
11150    format_1.await.unwrap();
11151    cx.executor().start_waiting();
11152    format_2.await.unwrap();
11153
11154    // The formatting edits only happens once.
11155    cx.assert_editor_state(indoc! {"
11156        one
11157            .twoˇ
11158    "});
11159}
11160
11161#[gpui::test]
11162async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
11163    init_test(cx, |settings| {
11164        settings.defaults.formatter = Some(SelectedFormatter::Auto)
11165    });
11166
11167    let mut cx = EditorLspTestContext::new_rust(
11168        lsp::ServerCapabilities {
11169            document_formatting_provider: Some(lsp::OneOf::Left(true)),
11170            ..Default::default()
11171        },
11172        cx,
11173    )
11174    .await;
11175
11176    // Set up a buffer white some trailing whitespace and no trailing newline.
11177    cx.set_state(
11178        &[
11179            "one ",   //
11180            "twoˇ",   //
11181            "three ", //
11182            "four",   //
11183        ]
11184        .join("\n"),
11185    );
11186
11187    // Submit a format request.
11188    let format = cx
11189        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
11190        .unwrap();
11191
11192    // Record which buffer changes have been sent to the language server
11193    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
11194    cx.lsp
11195        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
11196            let buffer_changes = buffer_changes.clone();
11197            move |params, _| {
11198                buffer_changes.lock().extend(
11199                    params
11200                        .content_changes
11201                        .into_iter()
11202                        .map(|e| (e.range.unwrap(), e.text)),
11203                );
11204            }
11205        });
11206
11207    // Handle formatting requests to the language server.
11208    cx.lsp
11209        .set_request_handler::<lsp::request::Formatting, _, _>({
11210            let buffer_changes = buffer_changes.clone();
11211            move |_, _| {
11212                // When formatting is requested, trailing whitespace has already been stripped,
11213                // and the trailing newline has already been added.
11214                assert_eq!(
11215                    &buffer_changes.lock()[1..],
11216                    &[
11217                        (
11218                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
11219                            "".into()
11220                        ),
11221                        (
11222                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
11223                            "".into()
11224                        ),
11225                        (
11226                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
11227                            "\n".into()
11228                        ),
11229                    ]
11230                );
11231
11232                // Insert blank lines between each line of the buffer.
11233                async move {
11234                    Ok(Some(vec![
11235                        lsp::TextEdit {
11236                            range: lsp::Range::new(
11237                                lsp::Position::new(1, 0),
11238                                lsp::Position::new(1, 0),
11239                            ),
11240                            new_text: "\n".into(),
11241                        },
11242                        lsp::TextEdit {
11243                            range: lsp::Range::new(
11244                                lsp::Position::new(2, 0),
11245                                lsp::Position::new(2, 0),
11246                            ),
11247                            new_text: "\n".into(),
11248                        },
11249                    ]))
11250                }
11251            }
11252        });
11253
11254    // After formatting the buffer, the trailing whitespace is stripped,
11255    // a newline is appended, and the edits provided by the language server
11256    // have been applied.
11257    format.await.unwrap();
11258    cx.assert_editor_state(
11259        &[
11260            "one",   //
11261            "",      //
11262            "twoˇ",  //
11263            "",      //
11264            "three", //
11265            "four",  //
11266            "",      //
11267        ]
11268        .join("\n"),
11269    );
11270
11271    // Undoing the formatting undoes the trailing whitespace removal, the
11272    // trailing newline, and the LSP edits.
11273    cx.update_buffer(|buffer, cx| buffer.undo(cx));
11274    cx.assert_editor_state(
11275        &[
11276            "one ",   //
11277            "twoˇ",   //
11278            "three ", //
11279            "four",   //
11280        ]
11281        .join("\n"),
11282    );
11283}
11284
11285#[gpui::test]
11286async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
11287    cx: &mut TestAppContext,
11288) {
11289    init_test(cx, |_| {});
11290
11291    cx.update(|cx| {
11292        cx.update_global::<SettingsStore, _>(|settings, cx| {
11293            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11294                settings.auto_signature_help = Some(true);
11295            });
11296        });
11297    });
11298
11299    let mut cx = EditorLspTestContext::new_rust(
11300        lsp::ServerCapabilities {
11301            signature_help_provider: Some(lsp::SignatureHelpOptions {
11302                ..Default::default()
11303            }),
11304            ..Default::default()
11305        },
11306        cx,
11307    )
11308    .await;
11309
11310    let language = Language::new(
11311        LanguageConfig {
11312            name: "Rust".into(),
11313            brackets: BracketPairConfig {
11314                pairs: vec![
11315                    BracketPair {
11316                        start: "{".to_string(),
11317                        end: "}".to_string(),
11318                        close: true,
11319                        surround: true,
11320                        newline: true,
11321                    },
11322                    BracketPair {
11323                        start: "(".to_string(),
11324                        end: ")".to_string(),
11325                        close: true,
11326                        surround: true,
11327                        newline: true,
11328                    },
11329                    BracketPair {
11330                        start: "/*".to_string(),
11331                        end: " */".to_string(),
11332                        close: true,
11333                        surround: true,
11334                        newline: true,
11335                    },
11336                    BracketPair {
11337                        start: "[".to_string(),
11338                        end: "]".to_string(),
11339                        close: false,
11340                        surround: false,
11341                        newline: true,
11342                    },
11343                    BracketPair {
11344                        start: "\"".to_string(),
11345                        end: "\"".to_string(),
11346                        close: true,
11347                        surround: true,
11348                        newline: false,
11349                    },
11350                    BracketPair {
11351                        start: "<".to_string(),
11352                        end: ">".to_string(),
11353                        close: false,
11354                        surround: true,
11355                        newline: true,
11356                    },
11357                ],
11358                ..Default::default()
11359            },
11360            autoclose_before: "})]".to_string(),
11361            ..Default::default()
11362        },
11363        Some(tree_sitter_rust::LANGUAGE.into()),
11364    );
11365    let language = Arc::new(language);
11366
11367    cx.language_registry().add(language.clone());
11368    cx.update_buffer(|buffer, cx| {
11369        buffer.set_language(Some(language), cx);
11370    });
11371
11372    cx.set_state(
11373        &r#"
11374            fn main() {
11375                sampleˇ
11376            }
11377        "#
11378        .unindent(),
11379    );
11380
11381    cx.update_editor(|editor, window, cx| {
11382        editor.handle_input("(", window, cx);
11383    });
11384    cx.assert_editor_state(
11385        &"
11386            fn main() {
11387                sample(ˇ)
11388            }
11389        "
11390        .unindent(),
11391    );
11392
11393    let mocked_response = lsp::SignatureHelp {
11394        signatures: vec![lsp::SignatureInformation {
11395            label: "fn sample(param1: u8, param2: u8)".to_string(),
11396            documentation: None,
11397            parameters: Some(vec![
11398                lsp::ParameterInformation {
11399                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11400                    documentation: None,
11401                },
11402                lsp::ParameterInformation {
11403                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11404                    documentation: None,
11405                },
11406            ]),
11407            active_parameter: None,
11408        }],
11409        active_signature: Some(0),
11410        active_parameter: Some(0),
11411    };
11412    handle_signature_help_request(&mut cx, mocked_response).await;
11413
11414    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11415        .await;
11416
11417    cx.editor(|editor, _, _| {
11418        let signature_help_state = editor.signature_help_state.popover().cloned();
11419        let signature = signature_help_state.unwrap();
11420        assert_eq!(
11421            signature.signatures[signature.current_signature].label,
11422            "fn sample(param1: u8, param2: u8)"
11423        );
11424    });
11425}
11426
11427#[gpui::test]
11428async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
11429    init_test(cx, |_| {});
11430
11431    cx.update(|cx| {
11432        cx.update_global::<SettingsStore, _>(|settings, cx| {
11433            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11434                settings.auto_signature_help = Some(false);
11435                settings.show_signature_help_after_edits = Some(false);
11436            });
11437        });
11438    });
11439
11440    let mut cx = EditorLspTestContext::new_rust(
11441        lsp::ServerCapabilities {
11442            signature_help_provider: Some(lsp::SignatureHelpOptions {
11443                ..Default::default()
11444            }),
11445            ..Default::default()
11446        },
11447        cx,
11448    )
11449    .await;
11450
11451    let language = Language::new(
11452        LanguageConfig {
11453            name: "Rust".into(),
11454            brackets: BracketPairConfig {
11455                pairs: vec![
11456                    BracketPair {
11457                        start: "{".to_string(),
11458                        end: "}".to_string(),
11459                        close: true,
11460                        surround: true,
11461                        newline: true,
11462                    },
11463                    BracketPair {
11464                        start: "(".to_string(),
11465                        end: ")".to_string(),
11466                        close: true,
11467                        surround: true,
11468                        newline: true,
11469                    },
11470                    BracketPair {
11471                        start: "/*".to_string(),
11472                        end: " */".to_string(),
11473                        close: true,
11474                        surround: true,
11475                        newline: true,
11476                    },
11477                    BracketPair {
11478                        start: "[".to_string(),
11479                        end: "]".to_string(),
11480                        close: false,
11481                        surround: false,
11482                        newline: true,
11483                    },
11484                    BracketPair {
11485                        start: "\"".to_string(),
11486                        end: "\"".to_string(),
11487                        close: true,
11488                        surround: true,
11489                        newline: false,
11490                    },
11491                    BracketPair {
11492                        start: "<".to_string(),
11493                        end: ">".to_string(),
11494                        close: false,
11495                        surround: true,
11496                        newline: true,
11497                    },
11498                ],
11499                ..Default::default()
11500            },
11501            autoclose_before: "})]".to_string(),
11502            ..Default::default()
11503        },
11504        Some(tree_sitter_rust::LANGUAGE.into()),
11505    );
11506    let language = Arc::new(language);
11507
11508    cx.language_registry().add(language.clone());
11509    cx.update_buffer(|buffer, cx| {
11510        buffer.set_language(Some(language), cx);
11511    });
11512
11513    // Ensure that signature_help is not called when no signature help is enabled.
11514    cx.set_state(
11515        &r#"
11516            fn main() {
11517                sampleˇ
11518            }
11519        "#
11520        .unindent(),
11521    );
11522    cx.update_editor(|editor, window, cx| {
11523        editor.handle_input("(", window, cx);
11524    });
11525    cx.assert_editor_state(
11526        &"
11527            fn main() {
11528                sample(ˇ)
11529            }
11530        "
11531        .unindent(),
11532    );
11533    cx.editor(|editor, _, _| {
11534        assert!(editor.signature_help_state.task().is_none());
11535    });
11536
11537    let mocked_response = lsp::SignatureHelp {
11538        signatures: vec![lsp::SignatureInformation {
11539            label: "fn sample(param1: u8, param2: u8)".to_string(),
11540            documentation: None,
11541            parameters: Some(vec![
11542                lsp::ParameterInformation {
11543                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11544                    documentation: None,
11545                },
11546                lsp::ParameterInformation {
11547                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11548                    documentation: None,
11549                },
11550            ]),
11551            active_parameter: None,
11552        }],
11553        active_signature: Some(0),
11554        active_parameter: Some(0),
11555    };
11556
11557    // Ensure that signature_help is called when enabled afte edits
11558    cx.update(|_, cx| {
11559        cx.update_global::<SettingsStore, _>(|settings, cx| {
11560            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11561                settings.auto_signature_help = Some(false);
11562                settings.show_signature_help_after_edits = Some(true);
11563            });
11564        });
11565    });
11566    cx.set_state(
11567        &r#"
11568            fn main() {
11569                sampleˇ
11570            }
11571        "#
11572        .unindent(),
11573    );
11574    cx.update_editor(|editor, window, cx| {
11575        editor.handle_input("(", window, cx);
11576    });
11577    cx.assert_editor_state(
11578        &"
11579            fn main() {
11580                sample(ˇ)
11581            }
11582        "
11583        .unindent(),
11584    );
11585    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11586    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11587        .await;
11588    cx.update_editor(|editor, _, _| {
11589        let signature_help_state = editor.signature_help_state.popover().cloned();
11590        assert!(signature_help_state.is_some());
11591        let signature = signature_help_state.unwrap();
11592        assert_eq!(
11593            signature.signatures[signature.current_signature].label,
11594            "fn sample(param1: u8, param2: u8)"
11595        );
11596        editor.signature_help_state = SignatureHelpState::default();
11597    });
11598
11599    // Ensure that signature_help is called when auto signature help override is enabled
11600    cx.update(|_, cx| {
11601        cx.update_global::<SettingsStore, _>(|settings, cx| {
11602            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11603                settings.auto_signature_help = Some(true);
11604                settings.show_signature_help_after_edits = Some(false);
11605            });
11606        });
11607    });
11608    cx.set_state(
11609        &r#"
11610            fn main() {
11611                sampleˇ
11612            }
11613        "#
11614        .unindent(),
11615    );
11616    cx.update_editor(|editor, window, cx| {
11617        editor.handle_input("(", window, cx);
11618    });
11619    cx.assert_editor_state(
11620        &"
11621            fn main() {
11622                sample(ˇ)
11623            }
11624        "
11625        .unindent(),
11626    );
11627    handle_signature_help_request(&mut cx, mocked_response).await;
11628    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11629        .await;
11630    cx.editor(|editor, _, _| {
11631        let signature_help_state = editor.signature_help_state.popover().cloned();
11632        assert!(signature_help_state.is_some());
11633        let signature = signature_help_state.unwrap();
11634        assert_eq!(
11635            signature.signatures[signature.current_signature].label,
11636            "fn sample(param1: u8, param2: u8)"
11637        );
11638    });
11639}
11640
11641#[gpui::test]
11642async fn test_signature_help(cx: &mut TestAppContext) {
11643    init_test(cx, |_| {});
11644    cx.update(|cx| {
11645        cx.update_global::<SettingsStore, _>(|settings, cx| {
11646            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11647                settings.auto_signature_help = Some(true);
11648            });
11649        });
11650    });
11651
11652    let mut cx = EditorLspTestContext::new_rust(
11653        lsp::ServerCapabilities {
11654            signature_help_provider: Some(lsp::SignatureHelpOptions {
11655                ..Default::default()
11656            }),
11657            ..Default::default()
11658        },
11659        cx,
11660    )
11661    .await;
11662
11663    // A test that directly calls `show_signature_help`
11664    cx.update_editor(|editor, window, cx| {
11665        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11666    });
11667
11668    let mocked_response = lsp::SignatureHelp {
11669        signatures: vec![lsp::SignatureInformation {
11670            label: "fn sample(param1: u8, param2: u8)".to_string(),
11671            documentation: None,
11672            parameters: Some(vec![
11673                lsp::ParameterInformation {
11674                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11675                    documentation: None,
11676                },
11677                lsp::ParameterInformation {
11678                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11679                    documentation: None,
11680                },
11681            ]),
11682            active_parameter: None,
11683        }],
11684        active_signature: Some(0),
11685        active_parameter: Some(0),
11686    };
11687    handle_signature_help_request(&mut cx, mocked_response).await;
11688
11689    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11690        .await;
11691
11692    cx.editor(|editor, _, _| {
11693        let signature_help_state = editor.signature_help_state.popover().cloned();
11694        assert!(signature_help_state.is_some());
11695        let signature = signature_help_state.unwrap();
11696        assert_eq!(
11697            signature.signatures[signature.current_signature].label,
11698            "fn sample(param1: u8, param2: u8)"
11699        );
11700    });
11701
11702    // When exiting outside from inside the brackets, `signature_help` is closed.
11703    cx.set_state(indoc! {"
11704        fn main() {
11705            sample(ˇ);
11706        }
11707
11708        fn sample(param1: u8, param2: u8) {}
11709    "});
11710
11711    cx.update_editor(|editor, window, cx| {
11712        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11713            s.select_ranges([0..0])
11714        });
11715    });
11716
11717    let mocked_response = lsp::SignatureHelp {
11718        signatures: Vec::new(),
11719        active_signature: None,
11720        active_parameter: None,
11721    };
11722    handle_signature_help_request(&mut cx, mocked_response).await;
11723
11724    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11725        .await;
11726
11727    cx.editor(|editor, _, _| {
11728        assert!(!editor.signature_help_state.is_shown());
11729    });
11730
11731    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
11732    cx.set_state(indoc! {"
11733        fn main() {
11734            sample(ˇ);
11735        }
11736
11737        fn sample(param1: u8, param2: u8) {}
11738    "});
11739
11740    let mocked_response = lsp::SignatureHelp {
11741        signatures: vec![lsp::SignatureInformation {
11742            label: "fn sample(param1: u8, param2: u8)".to_string(),
11743            documentation: None,
11744            parameters: Some(vec![
11745                lsp::ParameterInformation {
11746                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11747                    documentation: None,
11748                },
11749                lsp::ParameterInformation {
11750                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11751                    documentation: None,
11752                },
11753            ]),
11754            active_parameter: None,
11755        }],
11756        active_signature: Some(0),
11757        active_parameter: Some(0),
11758    };
11759    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11760    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11761        .await;
11762    cx.editor(|editor, _, _| {
11763        assert!(editor.signature_help_state.is_shown());
11764    });
11765
11766    // Restore the popover with more parameter input
11767    cx.set_state(indoc! {"
11768        fn main() {
11769            sample(param1, param2ˇ);
11770        }
11771
11772        fn sample(param1: u8, param2: u8) {}
11773    "});
11774
11775    let mocked_response = lsp::SignatureHelp {
11776        signatures: vec![lsp::SignatureInformation {
11777            label: "fn sample(param1: u8, param2: u8)".to_string(),
11778            documentation: None,
11779            parameters: Some(vec![
11780                lsp::ParameterInformation {
11781                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11782                    documentation: None,
11783                },
11784                lsp::ParameterInformation {
11785                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11786                    documentation: None,
11787                },
11788            ]),
11789            active_parameter: None,
11790        }],
11791        active_signature: Some(0),
11792        active_parameter: Some(1),
11793    };
11794    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11795    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11796        .await;
11797
11798    // When selecting a range, the popover is gone.
11799    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
11800    cx.update_editor(|editor, window, cx| {
11801        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11802            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11803        })
11804    });
11805    cx.assert_editor_state(indoc! {"
11806        fn main() {
11807            sample(param1, «ˇparam2»);
11808        }
11809
11810        fn sample(param1: u8, param2: u8) {}
11811    "});
11812    cx.editor(|editor, _, _| {
11813        assert!(!editor.signature_help_state.is_shown());
11814    });
11815
11816    // When unselecting again, the popover is back if within the brackets.
11817    cx.update_editor(|editor, window, cx| {
11818        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11819            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11820        })
11821    });
11822    cx.assert_editor_state(indoc! {"
11823        fn main() {
11824            sample(param1, ˇparam2);
11825        }
11826
11827        fn sample(param1: u8, param2: u8) {}
11828    "});
11829    handle_signature_help_request(&mut cx, mocked_response).await;
11830    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11831        .await;
11832    cx.editor(|editor, _, _| {
11833        assert!(editor.signature_help_state.is_shown());
11834    });
11835
11836    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
11837    cx.update_editor(|editor, window, cx| {
11838        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11839            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
11840            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11841        })
11842    });
11843    cx.assert_editor_state(indoc! {"
11844        fn main() {
11845            sample(param1, ˇparam2);
11846        }
11847
11848        fn sample(param1: u8, param2: u8) {}
11849    "});
11850
11851    let mocked_response = lsp::SignatureHelp {
11852        signatures: vec![lsp::SignatureInformation {
11853            label: "fn sample(param1: u8, param2: u8)".to_string(),
11854            documentation: None,
11855            parameters: Some(vec![
11856                lsp::ParameterInformation {
11857                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11858                    documentation: None,
11859                },
11860                lsp::ParameterInformation {
11861                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11862                    documentation: None,
11863                },
11864            ]),
11865            active_parameter: None,
11866        }],
11867        active_signature: Some(0),
11868        active_parameter: Some(1),
11869    };
11870    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11871    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11872        .await;
11873    cx.update_editor(|editor, _, cx| {
11874        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
11875    });
11876    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11877        .await;
11878    cx.update_editor(|editor, window, cx| {
11879        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11880            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11881        })
11882    });
11883    cx.assert_editor_state(indoc! {"
11884        fn main() {
11885            sample(param1, «ˇparam2»);
11886        }
11887
11888        fn sample(param1: u8, param2: u8) {}
11889    "});
11890    cx.update_editor(|editor, window, cx| {
11891        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11892            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11893        })
11894    });
11895    cx.assert_editor_state(indoc! {"
11896        fn main() {
11897            sample(param1, ˇparam2);
11898        }
11899
11900        fn sample(param1: u8, param2: u8) {}
11901    "});
11902    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
11903        .await;
11904}
11905
11906#[gpui::test]
11907async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
11908    init_test(cx, |_| {});
11909
11910    let mut cx = EditorLspTestContext::new_rust(
11911        lsp::ServerCapabilities {
11912            signature_help_provider: Some(lsp::SignatureHelpOptions {
11913                ..Default::default()
11914            }),
11915            ..Default::default()
11916        },
11917        cx,
11918    )
11919    .await;
11920
11921    cx.set_state(indoc! {"
11922        fn main() {
11923            overloadedˇ
11924        }
11925    "});
11926
11927    cx.update_editor(|editor, window, cx| {
11928        editor.handle_input("(", window, cx);
11929        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11930    });
11931
11932    // Mock response with 3 signatures
11933    let mocked_response = lsp::SignatureHelp {
11934        signatures: vec![
11935            lsp::SignatureInformation {
11936                label: "fn overloaded(x: i32)".to_string(),
11937                documentation: None,
11938                parameters: Some(vec![lsp::ParameterInformation {
11939                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11940                    documentation: None,
11941                }]),
11942                active_parameter: None,
11943            },
11944            lsp::SignatureInformation {
11945                label: "fn overloaded(x: i32, y: i32)".to_string(),
11946                documentation: None,
11947                parameters: Some(vec![
11948                    lsp::ParameterInformation {
11949                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11950                        documentation: None,
11951                    },
11952                    lsp::ParameterInformation {
11953                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
11954                        documentation: None,
11955                    },
11956                ]),
11957                active_parameter: None,
11958            },
11959            lsp::SignatureInformation {
11960                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
11961                documentation: None,
11962                parameters: Some(vec![
11963                    lsp::ParameterInformation {
11964                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11965                        documentation: None,
11966                    },
11967                    lsp::ParameterInformation {
11968                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
11969                        documentation: None,
11970                    },
11971                    lsp::ParameterInformation {
11972                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
11973                        documentation: None,
11974                    },
11975                ]),
11976                active_parameter: None,
11977            },
11978        ],
11979        active_signature: Some(1),
11980        active_parameter: Some(0),
11981    };
11982    handle_signature_help_request(&mut cx, mocked_response).await;
11983
11984    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11985        .await;
11986
11987    // Verify we have multiple signatures and the right one is selected
11988    cx.editor(|editor, _, _| {
11989        let popover = editor.signature_help_state.popover().cloned().unwrap();
11990        assert_eq!(popover.signatures.len(), 3);
11991        // active_signature was 1, so that should be the current
11992        assert_eq!(popover.current_signature, 1);
11993        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
11994        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
11995        assert_eq!(
11996            popover.signatures[2].label,
11997            "fn overloaded(x: i32, y: i32, z: i32)"
11998        );
11999    });
12000
12001    // Test navigation functionality
12002    cx.update_editor(|editor, window, cx| {
12003        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
12004    });
12005
12006    cx.editor(|editor, _, _| {
12007        let popover = editor.signature_help_state.popover().cloned().unwrap();
12008        assert_eq!(popover.current_signature, 2);
12009    });
12010
12011    // Test wrap around
12012    cx.update_editor(|editor, window, cx| {
12013        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
12014    });
12015
12016    cx.editor(|editor, _, _| {
12017        let popover = editor.signature_help_state.popover().cloned().unwrap();
12018        assert_eq!(popover.current_signature, 0);
12019    });
12020
12021    // Test previous navigation
12022    cx.update_editor(|editor, window, cx| {
12023        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
12024    });
12025
12026    cx.editor(|editor, _, _| {
12027        let popover = editor.signature_help_state.popover().cloned().unwrap();
12028        assert_eq!(popover.current_signature, 2);
12029    });
12030}
12031
12032#[gpui::test]
12033async fn test_completion_mode(cx: &mut TestAppContext) {
12034    init_test(cx, |_| {});
12035    let mut cx = EditorLspTestContext::new_rust(
12036        lsp::ServerCapabilities {
12037            completion_provider: Some(lsp::CompletionOptions {
12038                resolve_provider: Some(true),
12039                ..Default::default()
12040            }),
12041            ..Default::default()
12042        },
12043        cx,
12044    )
12045    .await;
12046
12047    struct Run {
12048        run_description: &'static str,
12049        initial_state: String,
12050        buffer_marked_text: String,
12051        completion_label: &'static str,
12052        completion_text: &'static str,
12053        expected_with_insert_mode: String,
12054        expected_with_replace_mode: String,
12055        expected_with_replace_subsequence_mode: String,
12056        expected_with_replace_suffix_mode: String,
12057    }
12058
12059    let runs = [
12060        Run {
12061            run_description: "Start of word matches completion text",
12062            initial_state: "before ediˇ after".into(),
12063            buffer_marked_text: "before <edi|> after".into(),
12064            completion_label: "editor",
12065            completion_text: "editor",
12066            expected_with_insert_mode: "before editorˇ after".into(),
12067            expected_with_replace_mode: "before editorˇ after".into(),
12068            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
12069            expected_with_replace_suffix_mode: "before editorˇ after".into(),
12070        },
12071        Run {
12072            run_description: "Accept same text at the middle of the word",
12073            initial_state: "before ediˇtor after".into(),
12074            buffer_marked_text: "before <edi|tor> after".into(),
12075            completion_label: "editor",
12076            completion_text: "editor",
12077            expected_with_insert_mode: "before editorˇtor after".into(),
12078            expected_with_replace_mode: "before editorˇ after".into(),
12079            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
12080            expected_with_replace_suffix_mode: "before editorˇ after".into(),
12081        },
12082        Run {
12083            run_description: "End of word matches completion text -- cursor at end",
12084            initial_state: "before torˇ after".into(),
12085            buffer_marked_text: "before <tor|> after".into(),
12086            completion_label: "editor",
12087            completion_text: "editor",
12088            expected_with_insert_mode: "before editorˇ after".into(),
12089            expected_with_replace_mode: "before editorˇ after".into(),
12090            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
12091            expected_with_replace_suffix_mode: "before editorˇ after".into(),
12092        },
12093        Run {
12094            run_description: "End of word matches completion text -- cursor at start",
12095            initial_state: "before ˇtor after".into(),
12096            buffer_marked_text: "before <|tor> after".into(),
12097            completion_label: "editor",
12098            completion_text: "editor",
12099            expected_with_insert_mode: "before editorˇtor after".into(),
12100            expected_with_replace_mode: "before editorˇ after".into(),
12101            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
12102            expected_with_replace_suffix_mode: "before editorˇ after".into(),
12103        },
12104        Run {
12105            run_description: "Prepend text containing whitespace",
12106            initial_state: "pˇfield: bool".into(),
12107            buffer_marked_text: "<p|field>: bool".into(),
12108            completion_label: "pub ",
12109            completion_text: "pub ",
12110            expected_with_insert_mode: "pub ˇfield: bool".into(),
12111            expected_with_replace_mode: "pub ˇ: bool".into(),
12112            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
12113            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
12114        },
12115        Run {
12116            run_description: "Add element to start of list",
12117            initial_state: "[element_ˇelement_2]".into(),
12118            buffer_marked_text: "[<element_|element_2>]".into(),
12119            completion_label: "element_1",
12120            completion_text: "element_1",
12121            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
12122            expected_with_replace_mode: "[element_1ˇ]".into(),
12123            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
12124            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
12125        },
12126        Run {
12127            run_description: "Add element to start of list -- first and second elements are equal",
12128            initial_state: "[elˇelement]".into(),
12129            buffer_marked_text: "[<el|element>]".into(),
12130            completion_label: "element",
12131            completion_text: "element",
12132            expected_with_insert_mode: "[elementˇelement]".into(),
12133            expected_with_replace_mode: "[elementˇ]".into(),
12134            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
12135            expected_with_replace_suffix_mode: "[elementˇ]".into(),
12136        },
12137        Run {
12138            run_description: "Ends with matching suffix",
12139            initial_state: "SubˇError".into(),
12140            buffer_marked_text: "<Sub|Error>".into(),
12141            completion_label: "SubscriptionError",
12142            completion_text: "SubscriptionError",
12143            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
12144            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
12145            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
12146            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
12147        },
12148        Run {
12149            run_description: "Suffix is a subsequence -- contiguous",
12150            initial_state: "SubˇErr".into(),
12151            buffer_marked_text: "<Sub|Err>".into(),
12152            completion_label: "SubscriptionError",
12153            completion_text: "SubscriptionError",
12154            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
12155            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
12156            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
12157            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
12158        },
12159        Run {
12160            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
12161            initial_state: "Suˇscrirr".into(),
12162            buffer_marked_text: "<Su|scrirr>".into(),
12163            completion_label: "SubscriptionError",
12164            completion_text: "SubscriptionError",
12165            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
12166            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
12167            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
12168            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
12169        },
12170        Run {
12171            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
12172            initial_state: "foo(indˇix)".into(),
12173            buffer_marked_text: "foo(<ind|ix>)".into(),
12174            completion_label: "node_index",
12175            completion_text: "node_index",
12176            expected_with_insert_mode: "foo(node_indexˇix)".into(),
12177            expected_with_replace_mode: "foo(node_indexˇ)".into(),
12178            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
12179            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
12180        },
12181        Run {
12182            run_description: "Replace range ends before cursor - should extend to cursor",
12183            initial_state: "before editˇo after".into(),
12184            buffer_marked_text: "before <{ed}>it|o after".into(),
12185            completion_label: "editor",
12186            completion_text: "editor",
12187            expected_with_insert_mode: "before editorˇo after".into(),
12188            expected_with_replace_mode: "before editorˇo after".into(),
12189            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
12190            expected_with_replace_suffix_mode: "before editorˇo after".into(),
12191        },
12192        Run {
12193            run_description: "Uses label for suffix matching",
12194            initial_state: "before ediˇtor after".into(),
12195            buffer_marked_text: "before <edi|tor> after".into(),
12196            completion_label: "editor",
12197            completion_text: "editor()",
12198            expected_with_insert_mode: "before editor()ˇtor after".into(),
12199            expected_with_replace_mode: "before editor()ˇ after".into(),
12200            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
12201            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
12202        },
12203        Run {
12204            run_description: "Case insensitive subsequence and suffix matching",
12205            initial_state: "before EDiˇtoR after".into(),
12206            buffer_marked_text: "before <EDi|toR> after".into(),
12207            completion_label: "editor",
12208            completion_text: "editor",
12209            expected_with_insert_mode: "before editorˇtoR after".into(),
12210            expected_with_replace_mode: "before editorˇ after".into(),
12211            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
12212            expected_with_replace_suffix_mode: "before editorˇ after".into(),
12213        },
12214    ];
12215
12216    for run in runs {
12217        let run_variations = [
12218            (LspInsertMode::Insert, run.expected_with_insert_mode),
12219            (LspInsertMode::Replace, run.expected_with_replace_mode),
12220            (
12221                LspInsertMode::ReplaceSubsequence,
12222                run.expected_with_replace_subsequence_mode,
12223            ),
12224            (
12225                LspInsertMode::ReplaceSuffix,
12226                run.expected_with_replace_suffix_mode,
12227            ),
12228        ];
12229
12230        for (lsp_insert_mode, expected_text) in run_variations {
12231            eprintln!(
12232                "run = {:?}, mode = {lsp_insert_mode:.?}",
12233                run.run_description,
12234            );
12235
12236            update_test_language_settings(&mut cx, |settings| {
12237                settings.defaults.completions = Some(CompletionSettings {
12238                    lsp_insert_mode,
12239                    words: WordsCompletionMode::Disabled,
12240                    lsp: true,
12241                    lsp_fetch_timeout_ms: 0,
12242                });
12243            });
12244
12245            cx.set_state(&run.initial_state);
12246            cx.update_editor(|editor, window, cx| {
12247                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12248            });
12249
12250            let counter = Arc::new(AtomicUsize::new(0));
12251            handle_completion_request_with_insert_and_replace(
12252                &mut cx,
12253                &run.buffer_marked_text,
12254                vec![(run.completion_label, run.completion_text)],
12255                counter.clone(),
12256            )
12257            .await;
12258            cx.condition(|editor, _| editor.context_menu_visible())
12259                .await;
12260            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12261
12262            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12263                editor
12264                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
12265                    .unwrap()
12266            });
12267            cx.assert_editor_state(&expected_text);
12268            handle_resolve_completion_request(&mut cx, None).await;
12269            apply_additional_edits.await.unwrap();
12270        }
12271    }
12272}
12273
12274#[gpui::test]
12275async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
12276    init_test(cx, |_| {});
12277    let mut cx = EditorLspTestContext::new_rust(
12278        lsp::ServerCapabilities {
12279            completion_provider: Some(lsp::CompletionOptions {
12280                resolve_provider: Some(true),
12281                ..Default::default()
12282            }),
12283            ..Default::default()
12284        },
12285        cx,
12286    )
12287    .await;
12288
12289    let initial_state = "SubˇError";
12290    let buffer_marked_text = "<Sub|Error>";
12291    let completion_text = "SubscriptionError";
12292    let expected_with_insert_mode = "SubscriptionErrorˇError";
12293    let expected_with_replace_mode = "SubscriptionErrorˇ";
12294
12295    update_test_language_settings(&mut cx, |settings| {
12296        settings.defaults.completions = Some(CompletionSettings {
12297            words: WordsCompletionMode::Disabled,
12298            // set the opposite here to ensure that the action is overriding the default behavior
12299            lsp_insert_mode: LspInsertMode::Insert,
12300            lsp: true,
12301            lsp_fetch_timeout_ms: 0,
12302        });
12303    });
12304
12305    cx.set_state(initial_state);
12306    cx.update_editor(|editor, window, cx| {
12307        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12308    });
12309
12310    let counter = Arc::new(AtomicUsize::new(0));
12311    handle_completion_request_with_insert_and_replace(
12312        &mut cx,
12313        buffer_marked_text,
12314        vec![(completion_text, completion_text)],
12315        counter.clone(),
12316    )
12317    .await;
12318    cx.condition(|editor, _| editor.context_menu_visible())
12319        .await;
12320    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12321
12322    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12323        editor
12324            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12325            .unwrap()
12326    });
12327    cx.assert_editor_state(expected_with_replace_mode);
12328    handle_resolve_completion_request(&mut cx, None).await;
12329    apply_additional_edits.await.unwrap();
12330
12331    update_test_language_settings(&mut cx, |settings| {
12332        settings.defaults.completions = Some(CompletionSettings {
12333            words: WordsCompletionMode::Disabled,
12334            // set the opposite here to ensure that the action is overriding the default behavior
12335            lsp_insert_mode: LspInsertMode::Replace,
12336            lsp: true,
12337            lsp_fetch_timeout_ms: 0,
12338        });
12339    });
12340
12341    cx.set_state(initial_state);
12342    cx.update_editor(|editor, window, cx| {
12343        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12344    });
12345    handle_completion_request_with_insert_and_replace(
12346        &mut cx,
12347        buffer_marked_text,
12348        vec![(completion_text, completion_text)],
12349        counter.clone(),
12350    )
12351    .await;
12352    cx.condition(|editor, _| editor.context_menu_visible())
12353        .await;
12354    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12355
12356    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12357        editor
12358            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
12359            .unwrap()
12360    });
12361    cx.assert_editor_state(expected_with_insert_mode);
12362    handle_resolve_completion_request(&mut cx, None).await;
12363    apply_additional_edits.await.unwrap();
12364}
12365
12366#[gpui::test]
12367async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
12368    init_test(cx, |_| {});
12369    let mut cx = EditorLspTestContext::new_rust(
12370        lsp::ServerCapabilities {
12371            completion_provider: Some(lsp::CompletionOptions {
12372                resolve_provider: Some(true),
12373                ..Default::default()
12374            }),
12375            ..Default::default()
12376        },
12377        cx,
12378    )
12379    .await;
12380
12381    // scenario: surrounding text matches completion text
12382    let completion_text = "to_offset";
12383    let initial_state = indoc! {"
12384        1. buf.to_offˇsuffix
12385        2. buf.to_offˇsuf
12386        3. buf.to_offˇfix
12387        4. buf.to_offˇ
12388        5. into_offˇensive
12389        6. ˇsuffix
12390        7. let ˇ //
12391        8. aaˇzz
12392        9. buf.to_off«zzzzzˇ»suffix
12393        10. buf.«ˇzzzzz»suffix
12394        11. to_off«ˇzzzzz»
12395
12396        buf.to_offˇsuffix  // newest cursor
12397    "};
12398    let completion_marked_buffer = indoc! {"
12399        1. buf.to_offsuffix
12400        2. buf.to_offsuf
12401        3. buf.to_offfix
12402        4. buf.to_off
12403        5. into_offensive
12404        6. suffix
12405        7. let  //
12406        8. aazz
12407        9. buf.to_offzzzzzsuffix
12408        10. buf.zzzzzsuffix
12409        11. to_offzzzzz
12410
12411        buf.<to_off|suffix>  // newest cursor
12412    "};
12413    let expected = indoc! {"
12414        1. buf.to_offsetˇ
12415        2. buf.to_offsetˇsuf
12416        3. buf.to_offsetˇfix
12417        4. buf.to_offsetˇ
12418        5. into_offsetˇensive
12419        6. to_offsetˇsuffix
12420        7. let to_offsetˇ //
12421        8. aato_offsetˇzz
12422        9. buf.to_offsetˇ
12423        10. buf.to_offsetˇsuffix
12424        11. to_offsetˇ
12425
12426        buf.to_offsetˇ  // newest cursor
12427    "};
12428    cx.set_state(initial_state);
12429    cx.update_editor(|editor, window, cx| {
12430        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12431    });
12432    handle_completion_request_with_insert_and_replace(
12433        &mut cx,
12434        completion_marked_buffer,
12435        vec![(completion_text, completion_text)],
12436        Arc::new(AtomicUsize::new(0)),
12437    )
12438    .await;
12439    cx.condition(|editor, _| editor.context_menu_visible())
12440        .await;
12441    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12442        editor
12443            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12444            .unwrap()
12445    });
12446    cx.assert_editor_state(expected);
12447    handle_resolve_completion_request(&mut cx, None).await;
12448    apply_additional_edits.await.unwrap();
12449
12450    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
12451    let completion_text = "foo_and_bar";
12452    let initial_state = indoc! {"
12453        1. ooanbˇ
12454        2. zooanbˇ
12455        3. ooanbˇz
12456        4. zooanbˇz
12457        5. ooanˇ
12458        6. oanbˇ
12459
12460        ooanbˇ
12461    "};
12462    let completion_marked_buffer = indoc! {"
12463        1. ooanb
12464        2. zooanb
12465        3. ooanbz
12466        4. zooanbz
12467        5. ooan
12468        6. oanb
12469
12470        <ooanb|>
12471    "};
12472    let expected = indoc! {"
12473        1. foo_and_barˇ
12474        2. zfoo_and_barˇ
12475        3. foo_and_barˇz
12476        4. zfoo_and_barˇz
12477        5. ooanfoo_and_barˇ
12478        6. oanbfoo_and_barˇ
12479
12480        foo_and_barˇ
12481    "};
12482    cx.set_state(initial_state);
12483    cx.update_editor(|editor, window, cx| {
12484        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12485    });
12486    handle_completion_request_with_insert_and_replace(
12487        &mut cx,
12488        completion_marked_buffer,
12489        vec![(completion_text, completion_text)],
12490        Arc::new(AtomicUsize::new(0)),
12491    )
12492    .await;
12493    cx.condition(|editor, _| editor.context_menu_visible())
12494        .await;
12495    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12496        editor
12497            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12498            .unwrap()
12499    });
12500    cx.assert_editor_state(expected);
12501    handle_resolve_completion_request(&mut cx, None).await;
12502    apply_additional_edits.await.unwrap();
12503
12504    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
12505    // (expects the same as if it was inserted at the end)
12506    let completion_text = "foo_and_bar";
12507    let initial_state = indoc! {"
12508        1. ooˇanb
12509        2. zooˇanb
12510        3. ooˇanbz
12511        4. zooˇanbz
12512
12513        ooˇanb
12514    "};
12515    let completion_marked_buffer = indoc! {"
12516        1. ooanb
12517        2. zooanb
12518        3. ooanbz
12519        4. zooanbz
12520
12521        <oo|anb>
12522    "};
12523    let expected = indoc! {"
12524        1. foo_and_barˇ
12525        2. zfoo_and_barˇ
12526        3. foo_and_barˇz
12527        4. zfoo_and_barˇz
12528
12529        foo_and_barˇ
12530    "};
12531    cx.set_state(initial_state);
12532    cx.update_editor(|editor, window, cx| {
12533        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12534    });
12535    handle_completion_request_with_insert_and_replace(
12536        &mut cx,
12537        completion_marked_buffer,
12538        vec![(completion_text, completion_text)],
12539        Arc::new(AtomicUsize::new(0)),
12540    )
12541    .await;
12542    cx.condition(|editor, _| editor.context_menu_visible())
12543        .await;
12544    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12545        editor
12546            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12547            .unwrap()
12548    });
12549    cx.assert_editor_state(expected);
12550    handle_resolve_completion_request(&mut cx, None).await;
12551    apply_additional_edits.await.unwrap();
12552}
12553
12554// This used to crash
12555#[gpui::test]
12556async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
12557    init_test(cx, |_| {});
12558
12559    let buffer_text = indoc! {"
12560        fn main() {
12561            10.satu;
12562
12563            //
12564            // separate cursors so they open in different excerpts (manually reproducible)
12565            //
12566
12567            10.satu20;
12568        }
12569    "};
12570    let multibuffer_text_with_selections = indoc! {"
12571        fn main() {
12572            10.satuˇ;
12573
12574            //
12575
12576            //
12577
12578            10.satuˇ20;
12579        }
12580    "};
12581    let expected_multibuffer = indoc! {"
12582        fn main() {
12583            10.saturating_sub()ˇ;
12584
12585            //
12586
12587            //
12588
12589            10.saturating_sub()ˇ;
12590        }
12591    "};
12592
12593    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
12594    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
12595
12596    let fs = FakeFs::new(cx.executor());
12597    fs.insert_tree(
12598        path!("/a"),
12599        json!({
12600            "main.rs": buffer_text,
12601        }),
12602    )
12603    .await;
12604
12605    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12606    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12607    language_registry.add(rust_lang());
12608    let mut fake_servers = language_registry.register_fake_lsp(
12609        "Rust",
12610        FakeLspAdapter {
12611            capabilities: lsp::ServerCapabilities {
12612                completion_provider: Some(lsp::CompletionOptions {
12613                    resolve_provider: None,
12614                    ..lsp::CompletionOptions::default()
12615                }),
12616                ..lsp::ServerCapabilities::default()
12617            },
12618            ..FakeLspAdapter::default()
12619        },
12620    );
12621    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12622    let cx = &mut VisualTestContext::from_window(*workspace, cx);
12623    let buffer = project
12624        .update(cx, |project, cx| {
12625            project.open_local_buffer(path!("/a/main.rs"), cx)
12626        })
12627        .await
12628        .unwrap();
12629
12630    let multi_buffer = cx.new(|cx| {
12631        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
12632        multi_buffer.push_excerpts(
12633            buffer.clone(),
12634            [ExcerptRange::new(0..first_excerpt_end)],
12635            cx,
12636        );
12637        multi_buffer.push_excerpts(
12638            buffer.clone(),
12639            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
12640            cx,
12641        );
12642        multi_buffer
12643    });
12644
12645    let editor = workspace
12646        .update(cx, |_, window, cx| {
12647            cx.new(|cx| {
12648                Editor::new(
12649                    EditorMode::Full {
12650                        scale_ui_elements_with_buffer_font_size: false,
12651                        show_active_line_background: false,
12652                        sized_by_content: false,
12653                    },
12654                    multi_buffer.clone(),
12655                    Some(project.clone()),
12656                    window,
12657                    cx,
12658                )
12659            })
12660        })
12661        .unwrap();
12662
12663    let pane = workspace
12664        .update(cx, |workspace, _, _| workspace.active_pane().clone())
12665        .unwrap();
12666    pane.update_in(cx, |pane, window, cx| {
12667        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
12668    });
12669
12670    let fake_server = fake_servers.next().await.unwrap();
12671
12672    editor.update_in(cx, |editor, window, cx| {
12673        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12674            s.select_ranges([
12675                Point::new(1, 11)..Point::new(1, 11),
12676                Point::new(7, 11)..Point::new(7, 11),
12677            ])
12678        });
12679
12680        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
12681    });
12682
12683    editor.update_in(cx, |editor, window, cx| {
12684        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12685    });
12686
12687    fake_server
12688        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
12689            let completion_item = lsp::CompletionItem {
12690                label: "saturating_sub()".into(),
12691                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
12692                    lsp::InsertReplaceEdit {
12693                        new_text: "saturating_sub()".to_owned(),
12694                        insert: lsp::Range::new(
12695                            lsp::Position::new(7, 7),
12696                            lsp::Position::new(7, 11),
12697                        ),
12698                        replace: lsp::Range::new(
12699                            lsp::Position::new(7, 7),
12700                            lsp::Position::new(7, 13),
12701                        ),
12702                    },
12703                )),
12704                ..lsp::CompletionItem::default()
12705            };
12706
12707            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
12708        })
12709        .next()
12710        .await
12711        .unwrap();
12712
12713    cx.condition(&editor, |editor, _| editor.context_menu_visible())
12714        .await;
12715
12716    editor
12717        .update_in(cx, |editor, window, cx| {
12718            editor
12719                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12720                .unwrap()
12721        })
12722        .await
12723        .unwrap();
12724
12725    editor.update(cx, |editor, cx| {
12726        assert_text_with_selections(editor, expected_multibuffer, cx);
12727    })
12728}
12729
12730#[gpui::test]
12731async fn test_completion(cx: &mut TestAppContext) {
12732    init_test(cx, |_| {});
12733
12734    let mut cx = EditorLspTestContext::new_rust(
12735        lsp::ServerCapabilities {
12736            completion_provider: Some(lsp::CompletionOptions {
12737                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12738                resolve_provider: Some(true),
12739                ..Default::default()
12740            }),
12741            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12742            ..Default::default()
12743        },
12744        cx,
12745    )
12746    .await;
12747    let counter = Arc::new(AtomicUsize::new(0));
12748
12749    cx.set_state(indoc! {"
12750        oneˇ
12751        two
12752        three
12753    "});
12754    cx.simulate_keystroke(".");
12755    handle_completion_request(
12756        indoc! {"
12757            one.|<>
12758            two
12759            three
12760        "},
12761        vec!["first_completion", "second_completion"],
12762        true,
12763        counter.clone(),
12764        &mut cx,
12765    )
12766    .await;
12767    cx.condition(|editor, _| editor.context_menu_visible())
12768        .await;
12769    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12770
12771    let _handler = handle_signature_help_request(
12772        &mut cx,
12773        lsp::SignatureHelp {
12774            signatures: vec![lsp::SignatureInformation {
12775                label: "test signature".to_string(),
12776                documentation: None,
12777                parameters: Some(vec![lsp::ParameterInformation {
12778                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
12779                    documentation: None,
12780                }]),
12781                active_parameter: None,
12782            }],
12783            active_signature: None,
12784            active_parameter: None,
12785        },
12786    );
12787    cx.update_editor(|editor, window, cx| {
12788        assert!(
12789            !editor.signature_help_state.is_shown(),
12790            "No signature help was called for"
12791        );
12792        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12793    });
12794    cx.run_until_parked();
12795    cx.update_editor(|editor, _, _| {
12796        assert!(
12797            !editor.signature_help_state.is_shown(),
12798            "No signature help should be shown when completions menu is open"
12799        );
12800    });
12801
12802    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12803        editor.context_menu_next(&Default::default(), window, cx);
12804        editor
12805            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12806            .unwrap()
12807    });
12808    cx.assert_editor_state(indoc! {"
12809        one.second_completionˇ
12810        two
12811        three
12812    "});
12813
12814    handle_resolve_completion_request(
12815        &mut cx,
12816        Some(vec![
12817            (
12818                //This overlaps with the primary completion edit which is
12819                //misbehavior from the LSP spec, test that we filter it out
12820                indoc! {"
12821                    one.second_ˇcompletion
12822                    two
12823                    threeˇ
12824                "},
12825                "overlapping additional edit",
12826            ),
12827            (
12828                indoc! {"
12829                    one.second_completion
12830                    two
12831                    threeˇ
12832                "},
12833                "\nadditional edit",
12834            ),
12835        ]),
12836    )
12837    .await;
12838    apply_additional_edits.await.unwrap();
12839    cx.assert_editor_state(indoc! {"
12840        one.second_completionˇ
12841        two
12842        three
12843        additional edit
12844    "});
12845
12846    cx.set_state(indoc! {"
12847        one.second_completion
12848        twoˇ
12849        threeˇ
12850        additional edit
12851    "});
12852    cx.simulate_keystroke(" ");
12853    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12854    cx.simulate_keystroke("s");
12855    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12856
12857    cx.assert_editor_state(indoc! {"
12858        one.second_completion
12859        two sˇ
12860        three sˇ
12861        additional edit
12862    "});
12863    handle_completion_request(
12864        indoc! {"
12865            one.second_completion
12866            two s
12867            three <s|>
12868            additional edit
12869        "},
12870        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12871        true,
12872        counter.clone(),
12873        &mut cx,
12874    )
12875    .await;
12876    cx.condition(|editor, _| editor.context_menu_visible())
12877        .await;
12878    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12879
12880    cx.simulate_keystroke("i");
12881
12882    handle_completion_request(
12883        indoc! {"
12884            one.second_completion
12885            two si
12886            three <si|>
12887            additional edit
12888        "},
12889        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12890        true,
12891        counter.clone(),
12892        &mut cx,
12893    )
12894    .await;
12895    cx.condition(|editor, _| editor.context_menu_visible())
12896        .await;
12897    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12898
12899    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12900        editor
12901            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12902            .unwrap()
12903    });
12904    cx.assert_editor_state(indoc! {"
12905        one.second_completion
12906        two sixth_completionˇ
12907        three sixth_completionˇ
12908        additional edit
12909    "});
12910
12911    apply_additional_edits.await.unwrap();
12912
12913    update_test_language_settings(&mut cx, |settings| {
12914        settings.defaults.show_completions_on_input = Some(false);
12915    });
12916    cx.set_state("editorˇ");
12917    cx.simulate_keystroke(".");
12918    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12919    cx.simulate_keystrokes("c l o");
12920    cx.assert_editor_state("editor.cloˇ");
12921    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12922    cx.update_editor(|editor, window, cx| {
12923        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12924    });
12925    handle_completion_request(
12926        "editor.<clo|>",
12927        vec!["close", "clobber"],
12928        true,
12929        counter.clone(),
12930        &mut cx,
12931    )
12932    .await;
12933    cx.condition(|editor, _| editor.context_menu_visible())
12934        .await;
12935    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
12936
12937    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12938        editor
12939            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12940            .unwrap()
12941    });
12942    cx.assert_editor_state("editor.clobberˇ");
12943    handle_resolve_completion_request(&mut cx, None).await;
12944    apply_additional_edits.await.unwrap();
12945}
12946
12947#[gpui::test]
12948async fn test_completion_reuse(cx: &mut TestAppContext) {
12949    init_test(cx, |_| {});
12950
12951    let mut cx = EditorLspTestContext::new_rust(
12952        lsp::ServerCapabilities {
12953            completion_provider: Some(lsp::CompletionOptions {
12954                trigger_characters: Some(vec![".".to_string()]),
12955                ..Default::default()
12956            }),
12957            ..Default::default()
12958        },
12959        cx,
12960    )
12961    .await;
12962
12963    let counter = Arc::new(AtomicUsize::new(0));
12964    cx.set_state("objˇ");
12965    cx.simulate_keystroke(".");
12966
12967    // Initial completion request returns complete results
12968    let is_incomplete = false;
12969    handle_completion_request(
12970        "obj.|<>",
12971        vec!["a", "ab", "abc"],
12972        is_incomplete,
12973        counter.clone(),
12974        &mut cx,
12975    )
12976    .await;
12977    cx.run_until_parked();
12978    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12979    cx.assert_editor_state("obj.ˇ");
12980    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12981
12982    // Type "a" - filters existing completions
12983    cx.simulate_keystroke("a");
12984    cx.run_until_parked();
12985    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12986    cx.assert_editor_state("obj.aˇ");
12987    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12988
12989    // Type "b" - filters existing completions
12990    cx.simulate_keystroke("b");
12991    cx.run_until_parked();
12992    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12993    cx.assert_editor_state("obj.abˇ");
12994    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12995
12996    // Type "c" - filters existing completions
12997    cx.simulate_keystroke("c");
12998    cx.run_until_parked();
12999    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13000    cx.assert_editor_state("obj.abcˇ");
13001    check_displayed_completions(vec!["abc"], &mut cx);
13002
13003    // Backspace to delete "c" - filters existing completions
13004    cx.update_editor(|editor, window, cx| {
13005        editor.backspace(&Backspace, window, cx);
13006    });
13007    cx.run_until_parked();
13008    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13009    cx.assert_editor_state("obj.abˇ");
13010    check_displayed_completions(vec!["ab", "abc"], &mut cx);
13011
13012    // Moving cursor to the left dismisses menu.
13013    cx.update_editor(|editor, window, cx| {
13014        editor.move_left(&MoveLeft, window, cx);
13015    });
13016    cx.run_until_parked();
13017    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13018    cx.assert_editor_state("obj.aˇb");
13019    cx.update_editor(|editor, _, _| {
13020        assert_eq!(editor.context_menu_visible(), false);
13021    });
13022
13023    // Type "b" - new request
13024    cx.simulate_keystroke("b");
13025    let is_incomplete = false;
13026    handle_completion_request(
13027        "obj.<ab|>a",
13028        vec!["ab", "abc"],
13029        is_incomplete,
13030        counter.clone(),
13031        &mut cx,
13032    )
13033    .await;
13034    cx.run_until_parked();
13035    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13036    cx.assert_editor_state("obj.abˇb");
13037    check_displayed_completions(vec!["ab", "abc"], &mut cx);
13038
13039    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
13040    cx.update_editor(|editor, window, cx| {
13041        editor.backspace(&Backspace, window, cx);
13042    });
13043    let is_incomplete = false;
13044    handle_completion_request(
13045        "obj.<a|>b",
13046        vec!["a", "ab", "abc"],
13047        is_incomplete,
13048        counter.clone(),
13049        &mut cx,
13050    )
13051    .await;
13052    cx.run_until_parked();
13053    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
13054    cx.assert_editor_state("obj.aˇb");
13055    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
13056
13057    // Backspace to delete "a" - dismisses menu.
13058    cx.update_editor(|editor, window, cx| {
13059        editor.backspace(&Backspace, window, cx);
13060    });
13061    cx.run_until_parked();
13062    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
13063    cx.assert_editor_state("obj.ˇb");
13064    cx.update_editor(|editor, _, _| {
13065        assert_eq!(editor.context_menu_visible(), false);
13066    });
13067}
13068
13069#[gpui::test]
13070async fn test_word_completion(cx: &mut TestAppContext) {
13071    let lsp_fetch_timeout_ms = 10;
13072    init_test(cx, |language_settings| {
13073        language_settings.defaults.completions = Some(CompletionSettings {
13074            words: WordsCompletionMode::Fallback,
13075            lsp: true,
13076            lsp_fetch_timeout_ms: 10,
13077            lsp_insert_mode: LspInsertMode::Insert,
13078        });
13079    });
13080
13081    let mut cx = EditorLspTestContext::new_rust(
13082        lsp::ServerCapabilities {
13083            completion_provider: Some(lsp::CompletionOptions {
13084                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13085                ..lsp::CompletionOptions::default()
13086            }),
13087            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13088            ..lsp::ServerCapabilities::default()
13089        },
13090        cx,
13091    )
13092    .await;
13093
13094    let throttle_completions = Arc::new(AtomicBool::new(false));
13095
13096    let lsp_throttle_completions = throttle_completions.clone();
13097    let _completion_requests_handler =
13098        cx.lsp
13099            .server
13100            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
13101                let lsp_throttle_completions = lsp_throttle_completions.clone();
13102                let cx = cx.clone();
13103                async move {
13104                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
13105                        cx.background_executor()
13106                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
13107                            .await;
13108                    }
13109                    Ok(Some(lsp::CompletionResponse::Array(vec![
13110                        lsp::CompletionItem {
13111                            label: "first".into(),
13112                            ..lsp::CompletionItem::default()
13113                        },
13114                        lsp::CompletionItem {
13115                            label: "last".into(),
13116                            ..lsp::CompletionItem::default()
13117                        },
13118                    ])))
13119                }
13120            });
13121
13122    cx.set_state(indoc! {"
13123        oneˇ
13124        two
13125        three
13126    "});
13127    cx.simulate_keystroke(".");
13128    cx.executor().run_until_parked();
13129    cx.condition(|editor, _| editor.context_menu_visible())
13130        .await;
13131    cx.update_editor(|editor, window, cx| {
13132        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13133        {
13134            assert_eq!(
13135                completion_menu_entries(menu),
13136                &["first", "last"],
13137                "When LSP server is fast to reply, no fallback word completions are used"
13138            );
13139        } else {
13140            panic!("expected completion menu to be open");
13141        }
13142        editor.cancel(&Cancel, window, cx);
13143    });
13144    cx.executor().run_until_parked();
13145    cx.condition(|editor, _| !editor.context_menu_visible())
13146        .await;
13147
13148    throttle_completions.store(true, atomic::Ordering::Release);
13149    cx.simulate_keystroke(".");
13150    cx.executor()
13151        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
13152    cx.executor().run_until_parked();
13153    cx.condition(|editor, _| editor.context_menu_visible())
13154        .await;
13155    cx.update_editor(|editor, _, _| {
13156        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13157        {
13158            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
13159                "When LSP server is slow, document words can be shown instead, if configured accordingly");
13160        } else {
13161            panic!("expected completion menu to be open");
13162        }
13163    });
13164}
13165
13166#[gpui::test]
13167async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
13168    init_test(cx, |language_settings| {
13169        language_settings.defaults.completions = Some(CompletionSettings {
13170            words: WordsCompletionMode::Enabled,
13171            lsp: true,
13172            lsp_fetch_timeout_ms: 0,
13173            lsp_insert_mode: LspInsertMode::Insert,
13174        });
13175    });
13176
13177    let mut cx = EditorLspTestContext::new_rust(
13178        lsp::ServerCapabilities {
13179            completion_provider: Some(lsp::CompletionOptions {
13180                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13181                ..lsp::CompletionOptions::default()
13182            }),
13183            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13184            ..lsp::ServerCapabilities::default()
13185        },
13186        cx,
13187    )
13188    .await;
13189
13190    let _completion_requests_handler =
13191        cx.lsp
13192            .server
13193            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
13194                Ok(Some(lsp::CompletionResponse::Array(vec![
13195                    lsp::CompletionItem {
13196                        label: "first".into(),
13197                        ..lsp::CompletionItem::default()
13198                    },
13199                    lsp::CompletionItem {
13200                        label: "last".into(),
13201                        ..lsp::CompletionItem::default()
13202                    },
13203                ])))
13204            });
13205
13206    cx.set_state(indoc! {"ˇ
13207        first
13208        last
13209        second
13210    "});
13211    cx.simulate_keystroke(".");
13212    cx.executor().run_until_parked();
13213    cx.condition(|editor, _| editor.context_menu_visible())
13214        .await;
13215    cx.update_editor(|editor, _, _| {
13216        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13217        {
13218            assert_eq!(
13219                completion_menu_entries(menu),
13220                &["first", "last", "second"],
13221                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
13222            );
13223        } else {
13224            panic!("expected completion menu to be open");
13225        }
13226    });
13227}
13228
13229#[gpui::test]
13230async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
13231    init_test(cx, |language_settings| {
13232        language_settings.defaults.completions = Some(CompletionSettings {
13233            words: WordsCompletionMode::Disabled,
13234            lsp: true,
13235            lsp_fetch_timeout_ms: 0,
13236            lsp_insert_mode: LspInsertMode::Insert,
13237        });
13238    });
13239
13240    let mut cx = EditorLspTestContext::new_rust(
13241        lsp::ServerCapabilities {
13242            completion_provider: Some(lsp::CompletionOptions {
13243                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13244                ..lsp::CompletionOptions::default()
13245            }),
13246            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13247            ..lsp::ServerCapabilities::default()
13248        },
13249        cx,
13250    )
13251    .await;
13252
13253    let _completion_requests_handler =
13254        cx.lsp
13255            .server
13256            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
13257                panic!("LSP completions should not be queried when dealing with word completions")
13258            });
13259
13260    cx.set_state(indoc! {"ˇ
13261        first
13262        last
13263        second
13264    "});
13265    cx.update_editor(|editor, window, cx| {
13266        editor.show_word_completions(&ShowWordCompletions, window, cx);
13267    });
13268    cx.executor().run_until_parked();
13269    cx.condition(|editor, _| editor.context_menu_visible())
13270        .await;
13271    cx.update_editor(|editor, _, _| {
13272        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13273        {
13274            assert_eq!(
13275                completion_menu_entries(menu),
13276                &["first", "last", "second"],
13277                "`ShowWordCompletions` action should show word completions"
13278            );
13279        } else {
13280            panic!("expected completion menu to be open");
13281        }
13282    });
13283
13284    cx.simulate_keystroke("l");
13285    cx.executor().run_until_parked();
13286    cx.condition(|editor, _| editor.context_menu_visible())
13287        .await;
13288    cx.update_editor(|editor, _, _| {
13289        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13290        {
13291            assert_eq!(
13292                completion_menu_entries(menu),
13293                &["last"],
13294                "After showing word completions, further editing should filter them and not query the LSP"
13295            );
13296        } else {
13297            panic!("expected completion menu to be open");
13298        }
13299    });
13300}
13301
13302#[gpui::test]
13303async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
13304    init_test(cx, |language_settings| {
13305        language_settings.defaults.completions = Some(CompletionSettings {
13306            words: WordsCompletionMode::Fallback,
13307            lsp: false,
13308            lsp_fetch_timeout_ms: 0,
13309            lsp_insert_mode: LspInsertMode::Insert,
13310        });
13311    });
13312
13313    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
13314
13315    cx.set_state(indoc! {"ˇ
13316        0_usize
13317        let
13318        33
13319        4.5f32
13320    "});
13321    cx.update_editor(|editor, window, cx| {
13322        editor.show_completions(&ShowCompletions::default(), window, cx);
13323    });
13324    cx.executor().run_until_parked();
13325    cx.condition(|editor, _| editor.context_menu_visible())
13326        .await;
13327    cx.update_editor(|editor, window, cx| {
13328        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13329        {
13330            assert_eq!(
13331                completion_menu_entries(menu),
13332                &["let"],
13333                "With no digits in the completion query, no digits should be in the word completions"
13334            );
13335        } else {
13336            panic!("expected completion menu to be open");
13337        }
13338        editor.cancel(&Cancel, window, cx);
13339    });
13340
13341    cx.set_state(indoc! {"13342        0_usize
13343        let
13344        3
13345        33.35f32
13346    "});
13347    cx.update_editor(|editor, window, cx| {
13348        editor.show_completions(&ShowCompletions::default(), window, cx);
13349    });
13350    cx.executor().run_until_parked();
13351    cx.condition(|editor, _| editor.context_menu_visible())
13352        .await;
13353    cx.update_editor(|editor, _, _| {
13354        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13355        {
13356            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
13357                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
13358        } else {
13359            panic!("expected completion menu to be open");
13360        }
13361    });
13362}
13363
13364fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
13365    let position = || lsp::Position {
13366        line: params.text_document_position.position.line,
13367        character: params.text_document_position.position.character,
13368    };
13369    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13370        range: lsp::Range {
13371            start: position(),
13372            end: position(),
13373        },
13374        new_text: text.to_string(),
13375    }))
13376}
13377
13378#[gpui::test]
13379async fn test_multiline_completion(cx: &mut TestAppContext) {
13380    init_test(cx, |_| {});
13381
13382    let fs = FakeFs::new(cx.executor());
13383    fs.insert_tree(
13384        path!("/a"),
13385        json!({
13386            "main.ts": "a",
13387        }),
13388    )
13389    .await;
13390
13391    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13392    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13393    let typescript_language = Arc::new(Language::new(
13394        LanguageConfig {
13395            name: "TypeScript".into(),
13396            matcher: LanguageMatcher {
13397                path_suffixes: vec!["ts".to_string()],
13398                ..LanguageMatcher::default()
13399            },
13400            line_comments: vec!["// ".into()],
13401            ..LanguageConfig::default()
13402        },
13403        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
13404    ));
13405    language_registry.add(typescript_language.clone());
13406    let mut fake_servers = language_registry.register_fake_lsp(
13407        "TypeScript",
13408        FakeLspAdapter {
13409            capabilities: lsp::ServerCapabilities {
13410                completion_provider: Some(lsp::CompletionOptions {
13411                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13412                    ..lsp::CompletionOptions::default()
13413                }),
13414                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13415                ..lsp::ServerCapabilities::default()
13416            },
13417            // Emulate vtsls label generation
13418            label_for_completion: Some(Box::new(|item, _| {
13419                let text = if let Some(description) = item
13420                    .label_details
13421                    .as_ref()
13422                    .and_then(|label_details| label_details.description.as_ref())
13423                {
13424                    format!("{} {}", item.label, description)
13425                } else if let Some(detail) = &item.detail {
13426                    format!("{} {}", item.label, detail)
13427                } else {
13428                    item.label.clone()
13429                };
13430                let len = text.len();
13431                Some(language::CodeLabel {
13432                    text,
13433                    runs: Vec::new(),
13434                    filter_range: 0..len,
13435                })
13436            })),
13437            ..FakeLspAdapter::default()
13438        },
13439    );
13440    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13441    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13442    let worktree_id = workspace
13443        .update(cx, |workspace, _window, cx| {
13444            workspace.project().update(cx, |project, cx| {
13445                project.worktrees(cx).next().unwrap().read(cx).id()
13446            })
13447        })
13448        .unwrap();
13449    let _buffer = project
13450        .update(cx, |project, cx| {
13451            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
13452        })
13453        .await
13454        .unwrap();
13455    let editor = workspace
13456        .update(cx, |workspace, window, cx| {
13457            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
13458        })
13459        .unwrap()
13460        .await
13461        .unwrap()
13462        .downcast::<Editor>()
13463        .unwrap();
13464    let fake_server = fake_servers.next().await.unwrap();
13465
13466    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
13467    let multiline_label_2 = "a\nb\nc\n";
13468    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
13469    let multiline_description = "d\ne\nf\n";
13470    let multiline_detail_2 = "g\nh\ni\n";
13471
13472    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
13473        move |params, _| async move {
13474            Ok(Some(lsp::CompletionResponse::Array(vec![
13475                lsp::CompletionItem {
13476                    label: multiline_label.to_string(),
13477                    text_edit: gen_text_edit(&params, "new_text_1"),
13478                    ..lsp::CompletionItem::default()
13479                },
13480                lsp::CompletionItem {
13481                    label: "single line label 1".to_string(),
13482                    detail: Some(multiline_detail.to_string()),
13483                    text_edit: gen_text_edit(&params, "new_text_2"),
13484                    ..lsp::CompletionItem::default()
13485                },
13486                lsp::CompletionItem {
13487                    label: "single line label 2".to_string(),
13488                    label_details: Some(lsp::CompletionItemLabelDetails {
13489                        description: Some(multiline_description.to_string()),
13490                        detail: None,
13491                    }),
13492                    text_edit: gen_text_edit(&params, "new_text_2"),
13493                    ..lsp::CompletionItem::default()
13494                },
13495                lsp::CompletionItem {
13496                    label: multiline_label_2.to_string(),
13497                    detail: Some(multiline_detail_2.to_string()),
13498                    text_edit: gen_text_edit(&params, "new_text_3"),
13499                    ..lsp::CompletionItem::default()
13500                },
13501                lsp::CompletionItem {
13502                    label: "Label with many     spaces and \t but without newlines".to_string(),
13503                    detail: Some(
13504                        "Details with many     spaces and \t but without newlines".to_string(),
13505                    ),
13506                    text_edit: gen_text_edit(&params, "new_text_4"),
13507                    ..lsp::CompletionItem::default()
13508                },
13509            ])))
13510        },
13511    );
13512
13513    editor.update_in(cx, |editor, window, cx| {
13514        cx.focus_self(window);
13515        editor.move_to_end(&MoveToEnd, window, cx);
13516        editor.handle_input(".", window, cx);
13517    });
13518    cx.run_until_parked();
13519    completion_handle.next().await.unwrap();
13520
13521    editor.update(cx, |editor, _| {
13522        assert!(editor.context_menu_visible());
13523        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13524        {
13525            let completion_labels = menu
13526                .completions
13527                .borrow()
13528                .iter()
13529                .map(|c| c.label.text.clone())
13530                .collect::<Vec<_>>();
13531            assert_eq!(
13532                completion_labels,
13533                &[
13534                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
13535                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
13536                    "single line label 2 d e f ",
13537                    "a b c g h i ",
13538                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
13539                ],
13540                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
13541            );
13542
13543            for completion in menu
13544                .completions
13545                .borrow()
13546                .iter() {
13547                    assert_eq!(
13548                        completion.label.filter_range,
13549                        0..completion.label.text.len(),
13550                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
13551                    );
13552                }
13553        } else {
13554            panic!("expected completion menu to be open");
13555        }
13556    });
13557}
13558
13559#[gpui::test]
13560async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
13561    init_test(cx, |_| {});
13562    let mut cx = EditorLspTestContext::new_rust(
13563        lsp::ServerCapabilities {
13564            completion_provider: Some(lsp::CompletionOptions {
13565                trigger_characters: Some(vec![".".to_string()]),
13566                ..Default::default()
13567            }),
13568            ..Default::default()
13569        },
13570        cx,
13571    )
13572    .await;
13573    cx.lsp
13574        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13575            Ok(Some(lsp::CompletionResponse::Array(vec![
13576                lsp::CompletionItem {
13577                    label: "first".into(),
13578                    ..Default::default()
13579                },
13580                lsp::CompletionItem {
13581                    label: "last".into(),
13582                    ..Default::default()
13583                },
13584            ])))
13585        });
13586    cx.set_state("variableˇ");
13587    cx.simulate_keystroke(".");
13588    cx.executor().run_until_parked();
13589
13590    cx.update_editor(|editor, _, _| {
13591        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13592        {
13593            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
13594        } else {
13595            panic!("expected completion menu to be open");
13596        }
13597    });
13598
13599    cx.update_editor(|editor, window, cx| {
13600        editor.move_page_down(&MovePageDown::default(), window, cx);
13601        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13602        {
13603            assert!(
13604                menu.selected_item == 1,
13605                "expected PageDown to select the last item from the context menu"
13606            );
13607        } else {
13608            panic!("expected completion menu to stay open after PageDown");
13609        }
13610    });
13611
13612    cx.update_editor(|editor, window, cx| {
13613        editor.move_page_up(&MovePageUp::default(), window, cx);
13614        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13615        {
13616            assert!(
13617                menu.selected_item == 0,
13618                "expected PageUp to select the first item from the context menu"
13619            );
13620        } else {
13621            panic!("expected completion menu to stay open after PageUp");
13622        }
13623    });
13624}
13625
13626#[gpui::test]
13627async fn test_as_is_completions(cx: &mut TestAppContext) {
13628    init_test(cx, |_| {});
13629    let mut cx = EditorLspTestContext::new_rust(
13630        lsp::ServerCapabilities {
13631            completion_provider: Some(lsp::CompletionOptions {
13632                ..Default::default()
13633            }),
13634            ..Default::default()
13635        },
13636        cx,
13637    )
13638    .await;
13639    cx.lsp
13640        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13641            Ok(Some(lsp::CompletionResponse::Array(vec![
13642                lsp::CompletionItem {
13643                    label: "unsafe".into(),
13644                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13645                        range: lsp::Range {
13646                            start: lsp::Position {
13647                                line: 1,
13648                                character: 2,
13649                            },
13650                            end: lsp::Position {
13651                                line: 1,
13652                                character: 3,
13653                            },
13654                        },
13655                        new_text: "unsafe".to_string(),
13656                    })),
13657                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
13658                    ..Default::default()
13659                },
13660            ])))
13661        });
13662    cx.set_state("fn a() {}\n");
13663    cx.executor().run_until_parked();
13664    cx.update_editor(|editor, window, cx| {
13665        editor.show_completions(
13666            &ShowCompletions {
13667                trigger: Some("\n".into()),
13668            },
13669            window,
13670            cx,
13671        );
13672    });
13673    cx.executor().run_until_parked();
13674
13675    cx.update_editor(|editor, window, cx| {
13676        editor.confirm_completion(&Default::default(), window, cx)
13677    });
13678    cx.executor().run_until_parked();
13679    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
13680}
13681
13682#[gpui::test]
13683async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
13684    init_test(cx, |_| {});
13685    let language =
13686        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
13687    let mut cx = EditorLspTestContext::new(
13688        language,
13689        lsp::ServerCapabilities {
13690            completion_provider: Some(lsp::CompletionOptions {
13691                ..lsp::CompletionOptions::default()
13692            }),
13693            ..lsp::ServerCapabilities::default()
13694        },
13695        cx,
13696    )
13697    .await;
13698
13699    cx.set_state(
13700        "#ifndef BAR_H
13701#define BAR_H
13702
13703#include <stdbool.h>
13704
13705int fn_branch(bool do_branch1, bool do_branch2);
13706
13707#endif // BAR_H
13708ˇ",
13709    );
13710    cx.executor().run_until_parked();
13711    cx.update_editor(|editor, window, cx| {
13712        editor.handle_input("#", window, cx);
13713    });
13714    cx.executor().run_until_parked();
13715    cx.update_editor(|editor, window, cx| {
13716        editor.handle_input("i", window, cx);
13717    });
13718    cx.executor().run_until_parked();
13719    cx.update_editor(|editor, window, cx| {
13720        editor.handle_input("n", window, cx);
13721    });
13722    cx.executor().run_until_parked();
13723    cx.assert_editor_state(
13724        "#ifndef BAR_H
13725#define BAR_H
13726
13727#include <stdbool.h>
13728
13729int fn_branch(bool do_branch1, bool do_branch2);
13730
13731#endif // BAR_H
13732#inˇ",
13733    );
13734
13735    cx.lsp
13736        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13737            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
13738                is_incomplete: false,
13739                item_defaults: None,
13740                items: vec![lsp::CompletionItem {
13741                    kind: Some(lsp::CompletionItemKind::SNIPPET),
13742                    label_details: Some(lsp::CompletionItemLabelDetails {
13743                        detail: Some("header".to_string()),
13744                        description: None,
13745                    }),
13746                    label: " include".to_string(),
13747                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13748                        range: lsp::Range {
13749                            start: lsp::Position {
13750                                line: 8,
13751                                character: 1,
13752                            },
13753                            end: lsp::Position {
13754                                line: 8,
13755                                character: 1,
13756                            },
13757                        },
13758                        new_text: "include \"$0\"".to_string(),
13759                    })),
13760                    sort_text: Some("40b67681include".to_string()),
13761                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
13762                    filter_text: Some("include".to_string()),
13763                    insert_text: Some("include \"$0\"".to_string()),
13764                    ..lsp::CompletionItem::default()
13765                }],
13766            })))
13767        });
13768    cx.update_editor(|editor, window, cx| {
13769        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13770    });
13771    cx.executor().run_until_parked();
13772    cx.update_editor(|editor, window, cx| {
13773        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
13774    });
13775    cx.executor().run_until_parked();
13776    cx.assert_editor_state(
13777        "#ifndef BAR_H
13778#define BAR_H
13779
13780#include <stdbool.h>
13781
13782int fn_branch(bool do_branch1, bool do_branch2);
13783
13784#endif // BAR_H
13785#include \"ˇ\"",
13786    );
13787
13788    cx.lsp
13789        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13790            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
13791                is_incomplete: true,
13792                item_defaults: None,
13793                items: vec![lsp::CompletionItem {
13794                    kind: Some(lsp::CompletionItemKind::FILE),
13795                    label: "AGL/".to_string(),
13796                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13797                        range: lsp::Range {
13798                            start: lsp::Position {
13799                                line: 8,
13800                                character: 10,
13801                            },
13802                            end: lsp::Position {
13803                                line: 8,
13804                                character: 11,
13805                            },
13806                        },
13807                        new_text: "AGL/".to_string(),
13808                    })),
13809                    sort_text: Some("40b67681AGL/".to_string()),
13810                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
13811                    filter_text: Some("AGL/".to_string()),
13812                    insert_text: Some("AGL/".to_string()),
13813                    ..lsp::CompletionItem::default()
13814                }],
13815            })))
13816        });
13817    cx.update_editor(|editor, window, cx| {
13818        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13819    });
13820    cx.executor().run_until_parked();
13821    cx.update_editor(|editor, window, cx| {
13822        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
13823    });
13824    cx.executor().run_until_parked();
13825    cx.assert_editor_state(
13826        r##"#ifndef BAR_H
13827#define BAR_H
13828
13829#include <stdbool.h>
13830
13831int fn_branch(bool do_branch1, bool do_branch2);
13832
13833#endif // BAR_H
13834#include "AGL/ˇ"##,
13835    );
13836
13837    cx.update_editor(|editor, window, cx| {
13838        editor.handle_input("\"", window, cx);
13839    });
13840    cx.executor().run_until_parked();
13841    cx.assert_editor_state(
13842        r##"#ifndef BAR_H
13843#define BAR_H
13844
13845#include <stdbool.h>
13846
13847int fn_branch(bool do_branch1, bool do_branch2);
13848
13849#endif // BAR_H
13850#include "AGL/"ˇ"##,
13851    );
13852}
13853
13854#[gpui::test]
13855async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
13856    init_test(cx, |_| {});
13857
13858    let mut cx = EditorLspTestContext::new_rust(
13859        lsp::ServerCapabilities {
13860            completion_provider: Some(lsp::CompletionOptions {
13861                trigger_characters: Some(vec![".".to_string()]),
13862                resolve_provider: Some(true),
13863                ..Default::default()
13864            }),
13865            ..Default::default()
13866        },
13867        cx,
13868    )
13869    .await;
13870
13871    cx.set_state("fn main() { let a = 2ˇ; }");
13872    cx.simulate_keystroke(".");
13873    let completion_item = lsp::CompletionItem {
13874        label: "Some".into(),
13875        kind: Some(lsp::CompletionItemKind::SNIPPET),
13876        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
13877        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
13878            kind: lsp::MarkupKind::Markdown,
13879            value: "```rust\nSome(2)\n```".to_string(),
13880        })),
13881        deprecated: Some(false),
13882        sort_text: Some("Some".to_string()),
13883        filter_text: Some("Some".to_string()),
13884        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
13885        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13886            range: lsp::Range {
13887                start: lsp::Position {
13888                    line: 0,
13889                    character: 22,
13890                },
13891                end: lsp::Position {
13892                    line: 0,
13893                    character: 22,
13894                },
13895            },
13896            new_text: "Some(2)".to_string(),
13897        })),
13898        additional_text_edits: Some(vec![lsp::TextEdit {
13899            range: lsp::Range {
13900                start: lsp::Position {
13901                    line: 0,
13902                    character: 20,
13903                },
13904                end: lsp::Position {
13905                    line: 0,
13906                    character: 22,
13907                },
13908            },
13909            new_text: "".to_string(),
13910        }]),
13911        ..Default::default()
13912    };
13913
13914    let closure_completion_item = completion_item.clone();
13915    let counter = Arc::new(AtomicUsize::new(0));
13916    let counter_clone = counter.clone();
13917    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
13918        let task_completion_item = closure_completion_item.clone();
13919        counter_clone.fetch_add(1, atomic::Ordering::Release);
13920        async move {
13921            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
13922                is_incomplete: true,
13923                item_defaults: None,
13924                items: vec![task_completion_item],
13925            })))
13926        }
13927    });
13928
13929    cx.condition(|editor, _| editor.context_menu_visible())
13930        .await;
13931    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
13932    assert!(request.next().await.is_some());
13933    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13934
13935    cx.simulate_keystrokes("S o m");
13936    cx.condition(|editor, _| editor.context_menu_visible())
13937        .await;
13938    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
13939    assert!(request.next().await.is_some());
13940    assert!(request.next().await.is_some());
13941    assert!(request.next().await.is_some());
13942    request.close();
13943    assert!(request.next().await.is_none());
13944    assert_eq!(
13945        counter.load(atomic::Ordering::Acquire),
13946        4,
13947        "With the completions menu open, only one LSP request should happen per input"
13948    );
13949}
13950
13951#[gpui::test]
13952async fn test_toggle_comment(cx: &mut TestAppContext) {
13953    init_test(cx, |_| {});
13954    let mut cx = EditorTestContext::new(cx).await;
13955    let language = Arc::new(Language::new(
13956        LanguageConfig {
13957            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
13958            ..Default::default()
13959        },
13960        Some(tree_sitter_rust::LANGUAGE.into()),
13961    ));
13962    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
13963
13964    // If multiple selections intersect a line, the line is only toggled once.
13965    cx.set_state(indoc! {"
13966        fn a() {
13967            «//b();
13968            ˇ»// «c();
13969            //ˇ»  d();
13970        }
13971    "});
13972
13973    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13974
13975    cx.assert_editor_state(indoc! {"
13976        fn a() {
13977            «b();
13978            c();
13979            ˇ» d();
13980        }
13981    "});
13982
13983    // The comment prefix is inserted at the same column for every line in a
13984    // selection.
13985    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13986
13987    cx.assert_editor_state(indoc! {"
13988        fn a() {
13989            // «b();
13990            // c();
13991            ˇ»//  d();
13992        }
13993    "});
13994
13995    // If a selection ends at the beginning of a line, that line is not toggled.
13996    cx.set_selections_state(indoc! {"
13997        fn a() {
13998            // b();
13999            «// c();
14000        ˇ»    //  d();
14001        }
14002    "});
14003
14004    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
14005
14006    cx.assert_editor_state(indoc! {"
14007        fn a() {
14008            // b();
14009            «c();
14010        ˇ»    //  d();
14011        }
14012    "});
14013
14014    // If a selection span a single line and is empty, the line is toggled.
14015    cx.set_state(indoc! {"
14016        fn a() {
14017            a();
14018            b();
14019        ˇ
14020        }
14021    "});
14022
14023    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
14024
14025    cx.assert_editor_state(indoc! {"
14026        fn a() {
14027            a();
14028            b();
14029        //•ˇ
14030        }
14031    "});
14032
14033    // If a selection span multiple lines, empty lines are not toggled.
14034    cx.set_state(indoc! {"
14035        fn a() {
14036            «a();
14037
14038            c();ˇ»
14039        }
14040    "});
14041
14042    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
14043
14044    cx.assert_editor_state(indoc! {"
14045        fn a() {
14046            // «a();
14047
14048            // c();ˇ»
14049        }
14050    "});
14051
14052    // If a selection includes multiple comment prefixes, all lines are uncommented.
14053    cx.set_state(indoc! {"
14054        fn a() {
14055            «// a();
14056            /// b();
14057            //! c();ˇ»
14058        }
14059    "});
14060
14061    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
14062
14063    cx.assert_editor_state(indoc! {"
14064        fn a() {
14065            «a();
14066            b();
14067            c();ˇ»
14068        }
14069    "});
14070}
14071
14072#[gpui::test]
14073async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
14074    init_test(cx, |_| {});
14075    let mut cx = EditorTestContext::new(cx).await;
14076    let language = Arc::new(Language::new(
14077        LanguageConfig {
14078            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
14079            ..Default::default()
14080        },
14081        Some(tree_sitter_rust::LANGUAGE.into()),
14082    ));
14083    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
14084
14085    let toggle_comments = &ToggleComments {
14086        advance_downwards: false,
14087        ignore_indent: true,
14088    };
14089
14090    // If multiple selections intersect a line, the line is only toggled once.
14091    cx.set_state(indoc! {"
14092        fn a() {
14093        //    «b();
14094        //    c();
14095        //    ˇ» d();
14096        }
14097    "});
14098
14099    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
14100
14101    cx.assert_editor_state(indoc! {"
14102        fn a() {
14103            «b();
14104            c();
14105            ˇ» d();
14106        }
14107    "});
14108
14109    // The comment prefix is inserted at the beginning of each line
14110    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
14111
14112    cx.assert_editor_state(indoc! {"
14113        fn a() {
14114        //    «b();
14115        //    c();
14116        //    ˇ» d();
14117        }
14118    "});
14119
14120    // If a selection ends at the beginning of a line, that line is not toggled.
14121    cx.set_selections_state(indoc! {"
14122        fn a() {
14123        //    b();
14124        //    «c();
14125        ˇ»//     d();
14126        }
14127    "});
14128
14129    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
14130
14131    cx.assert_editor_state(indoc! {"
14132        fn a() {
14133        //    b();
14134            «c();
14135        ˇ»//     d();
14136        }
14137    "});
14138
14139    // If a selection span a single line and is empty, the line is toggled.
14140    cx.set_state(indoc! {"
14141        fn a() {
14142            a();
14143            b();
14144        ˇ
14145        }
14146    "});
14147
14148    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
14149
14150    cx.assert_editor_state(indoc! {"
14151        fn a() {
14152            a();
14153            b();
14154        //ˇ
14155        }
14156    "});
14157
14158    // If a selection span multiple lines, empty lines are not toggled.
14159    cx.set_state(indoc! {"
14160        fn a() {
14161            «a();
14162
14163            c();ˇ»
14164        }
14165    "});
14166
14167    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
14168
14169    cx.assert_editor_state(indoc! {"
14170        fn a() {
14171        //    «a();
14172
14173        //    c();ˇ»
14174        }
14175    "});
14176
14177    // If a selection includes multiple comment prefixes, all lines are uncommented.
14178    cx.set_state(indoc! {"
14179        fn a() {
14180        //    «a();
14181        ///    b();
14182        //!    c();ˇ»
14183        }
14184    "});
14185
14186    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
14187
14188    cx.assert_editor_state(indoc! {"
14189        fn a() {
14190            «a();
14191            b();
14192            c();ˇ»
14193        }
14194    "});
14195}
14196
14197#[gpui::test]
14198async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
14199    init_test(cx, |_| {});
14200
14201    let language = Arc::new(Language::new(
14202        LanguageConfig {
14203            line_comments: vec!["// ".into()],
14204            ..Default::default()
14205        },
14206        Some(tree_sitter_rust::LANGUAGE.into()),
14207    ));
14208
14209    let mut cx = EditorTestContext::new(cx).await;
14210
14211    cx.language_registry().add(language.clone());
14212    cx.update_buffer(|buffer, cx| {
14213        buffer.set_language(Some(language), cx);
14214    });
14215
14216    let toggle_comments = &ToggleComments {
14217        advance_downwards: true,
14218        ignore_indent: false,
14219    };
14220
14221    // Single cursor on one line -> advance
14222    // Cursor moves horizontally 3 characters as well on non-blank line
14223    cx.set_state(indoc!(
14224        "fn a() {
14225             ˇdog();
14226             cat();
14227        }"
14228    ));
14229    cx.update_editor(|editor, window, cx| {
14230        editor.toggle_comments(toggle_comments, window, cx);
14231    });
14232    cx.assert_editor_state(indoc!(
14233        "fn a() {
14234             // dog();
14235             catˇ();
14236        }"
14237    ));
14238
14239    // Single selection on one line -> don't advance
14240    cx.set_state(indoc!(
14241        "fn a() {
14242             «dog()ˇ»;
14243             cat();
14244        }"
14245    ));
14246    cx.update_editor(|editor, window, cx| {
14247        editor.toggle_comments(toggle_comments, window, cx);
14248    });
14249    cx.assert_editor_state(indoc!(
14250        "fn a() {
14251             // «dog()ˇ»;
14252             cat();
14253        }"
14254    ));
14255
14256    // Multiple cursors on one line -> advance
14257    cx.set_state(indoc!(
14258        "fn a() {
14259             ˇdˇog();
14260             cat();
14261        }"
14262    ));
14263    cx.update_editor(|editor, window, cx| {
14264        editor.toggle_comments(toggle_comments, window, cx);
14265    });
14266    cx.assert_editor_state(indoc!(
14267        "fn a() {
14268             // dog();
14269             catˇ(ˇ);
14270        }"
14271    ));
14272
14273    // Multiple cursors on one line, with selection -> don't advance
14274    cx.set_state(indoc!(
14275        "fn a() {
14276             ˇdˇog«()ˇ»;
14277             cat();
14278        }"
14279    ));
14280    cx.update_editor(|editor, window, cx| {
14281        editor.toggle_comments(toggle_comments, window, cx);
14282    });
14283    cx.assert_editor_state(indoc!(
14284        "fn a() {
14285             // ˇdˇog«()ˇ»;
14286             cat();
14287        }"
14288    ));
14289
14290    // Single cursor on one line -> advance
14291    // Cursor moves to column 0 on blank line
14292    cx.set_state(indoc!(
14293        "fn a() {
14294             ˇdog();
14295
14296             cat();
14297        }"
14298    ));
14299    cx.update_editor(|editor, window, cx| {
14300        editor.toggle_comments(toggle_comments, window, cx);
14301    });
14302    cx.assert_editor_state(indoc!(
14303        "fn a() {
14304             // dog();
14305        ˇ
14306             cat();
14307        }"
14308    ));
14309
14310    // Single cursor on one line -> advance
14311    // Cursor starts and ends at column 0
14312    cx.set_state(indoc!(
14313        "fn a() {
14314         ˇ    dog();
14315             cat();
14316        }"
14317    ));
14318    cx.update_editor(|editor, window, cx| {
14319        editor.toggle_comments(toggle_comments, window, cx);
14320    });
14321    cx.assert_editor_state(indoc!(
14322        "fn a() {
14323             // dog();
14324         ˇ    cat();
14325        }"
14326    ));
14327}
14328
14329#[gpui::test]
14330async fn test_toggle_block_comment(cx: &mut TestAppContext) {
14331    init_test(cx, |_| {});
14332
14333    let mut cx = EditorTestContext::new(cx).await;
14334
14335    let html_language = Arc::new(
14336        Language::new(
14337            LanguageConfig {
14338                name: "HTML".into(),
14339                block_comment: Some(BlockCommentConfig {
14340                    start: "<!-- ".into(),
14341                    prefix: "".into(),
14342                    end: " -->".into(),
14343                    tab_size: 0,
14344                }),
14345                ..Default::default()
14346            },
14347            Some(tree_sitter_html::LANGUAGE.into()),
14348        )
14349        .with_injection_query(
14350            r#"
14351            (script_element
14352                (raw_text) @injection.content
14353                (#set! injection.language "javascript"))
14354            "#,
14355        )
14356        .unwrap(),
14357    );
14358
14359    let javascript_language = Arc::new(Language::new(
14360        LanguageConfig {
14361            name: "JavaScript".into(),
14362            line_comments: vec!["// ".into()],
14363            ..Default::default()
14364        },
14365        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
14366    ));
14367
14368    cx.language_registry().add(html_language.clone());
14369    cx.language_registry().add(javascript_language.clone());
14370    cx.update_buffer(|buffer, cx| {
14371        buffer.set_language(Some(html_language), cx);
14372    });
14373
14374    // Toggle comments for empty selections
14375    cx.set_state(
14376        &r#"
14377            <p>A</p>ˇ
14378            <p>B</p>ˇ
14379            <p>C</p>ˇ
14380        "#
14381        .unindent(),
14382    );
14383    cx.update_editor(|editor, window, cx| {
14384        editor.toggle_comments(&ToggleComments::default(), window, cx)
14385    });
14386    cx.assert_editor_state(
14387        &r#"
14388            <!-- <p>A</p>ˇ -->
14389            <!-- <p>B</p>ˇ -->
14390            <!-- <p>C</p>ˇ -->
14391        "#
14392        .unindent(),
14393    );
14394    cx.update_editor(|editor, window, cx| {
14395        editor.toggle_comments(&ToggleComments::default(), window, cx)
14396    });
14397    cx.assert_editor_state(
14398        &r#"
14399            <p>A</p>ˇ
14400            <p>B</p>ˇ
14401            <p>C</p>ˇ
14402        "#
14403        .unindent(),
14404    );
14405
14406    // Toggle comments for mixture of empty and non-empty selections, where
14407    // multiple selections occupy a given line.
14408    cx.set_state(
14409        &r#"
14410            <p>A«</p>
14411            <p>ˇ»B</p>ˇ
14412            <p>C«</p>
14413            <p>ˇ»D</p>ˇ
14414        "#
14415        .unindent(),
14416    );
14417
14418    cx.update_editor(|editor, window, cx| {
14419        editor.toggle_comments(&ToggleComments::default(), window, cx)
14420    });
14421    cx.assert_editor_state(
14422        &r#"
14423            <!-- <p>A«</p>
14424            <p>ˇ»B</p>ˇ -->
14425            <!-- <p>C«</p>
14426            <p>ˇ»D</p>ˇ -->
14427        "#
14428        .unindent(),
14429    );
14430    cx.update_editor(|editor, window, cx| {
14431        editor.toggle_comments(&ToggleComments::default(), window, cx)
14432    });
14433    cx.assert_editor_state(
14434        &r#"
14435            <p>A«</p>
14436            <p>ˇ»B</p>ˇ
14437            <p>C«</p>
14438            <p>ˇ»D</p>ˇ
14439        "#
14440        .unindent(),
14441    );
14442
14443    // Toggle comments when different languages are active for different
14444    // selections.
14445    cx.set_state(
14446        &r#"
14447            ˇ<script>
14448                ˇvar x = new Y();
14449            ˇ</script>
14450        "#
14451        .unindent(),
14452    );
14453    cx.executor().run_until_parked();
14454    cx.update_editor(|editor, window, cx| {
14455        editor.toggle_comments(&ToggleComments::default(), window, cx)
14456    });
14457    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
14458    // Uncommenting and commenting from this position brings in even more wrong artifacts.
14459    cx.assert_editor_state(
14460        &r#"
14461            <!-- ˇ<script> -->
14462                // ˇvar x = new Y();
14463            <!-- ˇ</script> -->
14464        "#
14465        .unindent(),
14466    );
14467}
14468
14469#[gpui::test]
14470fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
14471    init_test(cx, |_| {});
14472
14473    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
14474    let multibuffer = cx.new(|cx| {
14475        let mut multibuffer = MultiBuffer::new(ReadWrite);
14476        multibuffer.push_excerpts(
14477            buffer.clone(),
14478            [
14479                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
14480                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
14481            ],
14482            cx,
14483        );
14484        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
14485        multibuffer
14486    });
14487
14488    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
14489    editor.update_in(cx, |editor, window, cx| {
14490        assert_eq!(editor.text(cx), "aaaa\nbbbb");
14491        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14492            s.select_ranges([
14493                Point::new(0, 0)..Point::new(0, 0),
14494                Point::new(1, 0)..Point::new(1, 0),
14495            ])
14496        });
14497
14498        editor.handle_input("X", window, cx);
14499        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
14500        assert_eq!(
14501            editor.selections.ranges(cx),
14502            [
14503                Point::new(0, 1)..Point::new(0, 1),
14504                Point::new(1, 1)..Point::new(1, 1),
14505            ]
14506        );
14507
14508        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
14509        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14510            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
14511        });
14512        editor.backspace(&Default::default(), window, cx);
14513        assert_eq!(editor.text(cx), "Xa\nbbb");
14514        assert_eq!(
14515            editor.selections.ranges(cx),
14516            [Point::new(1, 0)..Point::new(1, 0)]
14517        );
14518
14519        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14520            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
14521        });
14522        editor.backspace(&Default::default(), window, cx);
14523        assert_eq!(editor.text(cx), "X\nbb");
14524        assert_eq!(
14525            editor.selections.ranges(cx),
14526            [Point::new(0, 1)..Point::new(0, 1)]
14527        );
14528    });
14529}
14530
14531#[gpui::test]
14532fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
14533    init_test(cx, |_| {});
14534
14535    let markers = vec![('[', ']').into(), ('(', ')').into()];
14536    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
14537        indoc! {"
14538            [aaaa
14539            (bbbb]
14540            cccc)",
14541        },
14542        markers.clone(),
14543    );
14544    let excerpt_ranges = markers.into_iter().map(|marker| {
14545        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
14546        ExcerptRange::new(context.clone())
14547    });
14548    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
14549    let multibuffer = cx.new(|cx| {
14550        let mut multibuffer = MultiBuffer::new(ReadWrite);
14551        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
14552        multibuffer
14553    });
14554
14555    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
14556    editor.update_in(cx, |editor, window, cx| {
14557        let (expected_text, selection_ranges) = marked_text_ranges(
14558            indoc! {"
14559                aaaa
14560                bˇbbb
14561                bˇbbˇb
14562                cccc"
14563            },
14564            true,
14565        );
14566        assert_eq!(editor.text(cx), expected_text);
14567        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14568            s.select_ranges(selection_ranges)
14569        });
14570
14571        editor.handle_input("X", window, cx);
14572
14573        let (expected_text, expected_selections) = marked_text_ranges(
14574            indoc! {"
14575                aaaa
14576                bXˇbbXb
14577                bXˇbbXˇb
14578                cccc"
14579            },
14580            false,
14581        );
14582        assert_eq!(editor.text(cx), expected_text);
14583        assert_eq!(editor.selections.ranges(cx), expected_selections);
14584
14585        editor.newline(&Newline, window, cx);
14586        let (expected_text, expected_selections) = marked_text_ranges(
14587            indoc! {"
14588                aaaa
14589                bX
14590                ˇbbX
14591                b
14592                bX
14593                ˇbbX
14594                ˇb
14595                cccc"
14596            },
14597            false,
14598        );
14599        assert_eq!(editor.text(cx), expected_text);
14600        assert_eq!(editor.selections.ranges(cx), expected_selections);
14601    });
14602}
14603
14604#[gpui::test]
14605fn test_refresh_selections(cx: &mut TestAppContext) {
14606    init_test(cx, |_| {});
14607
14608    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
14609    let mut excerpt1_id = None;
14610    let multibuffer = cx.new(|cx| {
14611        let mut multibuffer = MultiBuffer::new(ReadWrite);
14612        excerpt1_id = multibuffer
14613            .push_excerpts(
14614                buffer.clone(),
14615                [
14616                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
14617                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
14618                ],
14619                cx,
14620            )
14621            .into_iter()
14622            .next();
14623        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
14624        multibuffer
14625    });
14626
14627    let editor = cx.add_window(|window, cx| {
14628        let mut editor = build_editor(multibuffer.clone(), window, cx);
14629        let snapshot = editor.snapshot(window, cx);
14630        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14631            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
14632        });
14633        editor.begin_selection(
14634            Point::new(2, 1).to_display_point(&snapshot),
14635            true,
14636            1,
14637            window,
14638            cx,
14639        );
14640        assert_eq!(
14641            editor.selections.ranges(cx),
14642            [
14643                Point::new(1, 3)..Point::new(1, 3),
14644                Point::new(2, 1)..Point::new(2, 1),
14645            ]
14646        );
14647        editor
14648    });
14649
14650    // Refreshing selections is a no-op when excerpts haven't changed.
14651    _ = editor.update(cx, |editor, window, cx| {
14652        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14653        assert_eq!(
14654            editor.selections.ranges(cx),
14655            [
14656                Point::new(1, 3)..Point::new(1, 3),
14657                Point::new(2, 1)..Point::new(2, 1),
14658            ]
14659        );
14660    });
14661
14662    multibuffer.update(cx, |multibuffer, cx| {
14663        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
14664    });
14665    _ = editor.update(cx, |editor, window, cx| {
14666        // Removing an excerpt causes the first selection to become degenerate.
14667        assert_eq!(
14668            editor.selections.ranges(cx),
14669            [
14670                Point::new(0, 0)..Point::new(0, 0),
14671                Point::new(0, 1)..Point::new(0, 1)
14672            ]
14673        );
14674
14675        // Refreshing selections will relocate the first selection to the original buffer
14676        // location.
14677        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14678        assert_eq!(
14679            editor.selections.ranges(cx),
14680            [
14681                Point::new(0, 1)..Point::new(0, 1),
14682                Point::new(0, 3)..Point::new(0, 3)
14683            ]
14684        );
14685        assert!(editor.selections.pending_anchor().is_some());
14686    });
14687}
14688
14689#[gpui::test]
14690fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
14691    init_test(cx, |_| {});
14692
14693    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
14694    let mut excerpt1_id = None;
14695    let multibuffer = cx.new(|cx| {
14696        let mut multibuffer = MultiBuffer::new(ReadWrite);
14697        excerpt1_id = multibuffer
14698            .push_excerpts(
14699                buffer.clone(),
14700                [
14701                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
14702                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
14703                ],
14704                cx,
14705            )
14706            .into_iter()
14707            .next();
14708        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
14709        multibuffer
14710    });
14711
14712    let editor = cx.add_window(|window, cx| {
14713        let mut editor = build_editor(multibuffer.clone(), window, cx);
14714        let snapshot = editor.snapshot(window, cx);
14715        editor.begin_selection(
14716            Point::new(1, 3).to_display_point(&snapshot),
14717            false,
14718            1,
14719            window,
14720            cx,
14721        );
14722        assert_eq!(
14723            editor.selections.ranges(cx),
14724            [Point::new(1, 3)..Point::new(1, 3)]
14725        );
14726        editor
14727    });
14728
14729    multibuffer.update(cx, |multibuffer, cx| {
14730        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
14731    });
14732    _ = editor.update(cx, |editor, window, cx| {
14733        assert_eq!(
14734            editor.selections.ranges(cx),
14735            [Point::new(0, 0)..Point::new(0, 0)]
14736        );
14737
14738        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
14739        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14740        assert_eq!(
14741            editor.selections.ranges(cx),
14742            [Point::new(0, 3)..Point::new(0, 3)]
14743        );
14744        assert!(editor.selections.pending_anchor().is_some());
14745    });
14746}
14747
14748#[gpui::test]
14749async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
14750    init_test(cx, |_| {});
14751
14752    let language = Arc::new(
14753        Language::new(
14754            LanguageConfig {
14755                brackets: BracketPairConfig {
14756                    pairs: vec![
14757                        BracketPair {
14758                            start: "{".to_string(),
14759                            end: "}".to_string(),
14760                            close: true,
14761                            surround: true,
14762                            newline: true,
14763                        },
14764                        BracketPair {
14765                            start: "/* ".to_string(),
14766                            end: " */".to_string(),
14767                            close: true,
14768                            surround: true,
14769                            newline: true,
14770                        },
14771                    ],
14772                    ..Default::default()
14773                },
14774                ..Default::default()
14775            },
14776            Some(tree_sitter_rust::LANGUAGE.into()),
14777        )
14778        .with_indents_query("")
14779        .unwrap(),
14780    );
14781
14782    let text = concat!(
14783        "{   }\n",     //
14784        "  x\n",       //
14785        "  /*   */\n", //
14786        "x\n",         //
14787        "{{} }\n",     //
14788    );
14789
14790    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
14791    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
14792    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
14793    editor
14794        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
14795        .await;
14796
14797    editor.update_in(cx, |editor, window, cx| {
14798        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14799            s.select_display_ranges([
14800                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
14801                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
14802                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
14803            ])
14804        });
14805        editor.newline(&Newline, window, cx);
14806
14807        assert_eq!(
14808            editor.buffer().read(cx).read(cx).text(),
14809            concat!(
14810                "{ \n",    // Suppress rustfmt
14811                "\n",      //
14812                "}\n",     //
14813                "  x\n",   //
14814                "  /* \n", //
14815                "  \n",    //
14816                "  */\n",  //
14817                "x\n",     //
14818                "{{} \n",  //
14819                "}\n",     //
14820            )
14821        );
14822    });
14823}
14824
14825#[gpui::test]
14826fn test_highlighted_ranges(cx: &mut TestAppContext) {
14827    init_test(cx, |_| {});
14828
14829    let editor = cx.add_window(|window, cx| {
14830        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
14831        build_editor(buffer.clone(), window, cx)
14832    });
14833
14834    _ = editor.update(cx, |editor, window, cx| {
14835        struct Type1;
14836        struct Type2;
14837
14838        let buffer = editor.buffer.read(cx).snapshot(cx);
14839
14840        let anchor_range =
14841            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
14842
14843        editor.highlight_background::<Type1>(
14844            &[
14845                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
14846                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
14847                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
14848                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
14849            ],
14850            |_| Hsla::red(),
14851            cx,
14852        );
14853        editor.highlight_background::<Type2>(
14854            &[
14855                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
14856                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
14857                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
14858                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
14859            ],
14860            |_| Hsla::green(),
14861            cx,
14862        );
14863
14864        let snapshot = editor.snapshot(window, cx);
14865        let mut highlighted_ranges = editor.background_highlights_in_range(
14866            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
14867            &snapshot,
14868            cx.theme(),
14869        );
14870        // Enforce a consistent ordering based on color without relying on the ordering of the
14871        // highlight's `TypeId` which is non-executor.
14872        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
14873        assert_eq!(
14874            highlighted_ranges,
14875            &[
14876                (
14877                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
14878                    Hsla::red(),
14879                ),
14880                (
14881                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
14882                    Hsla::red(),
14883                ),
14884                (
14885                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
14886                    Hsla::green(),
14887                ),
14888                (
14889                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
14890                    Hsla::green(),
14891                ),
14892            ]
14893        );
14894        assert_eq!(
14895            editor.background_highlights_in_range(
14896                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
14897                &snapshot,
14898                cx.theme(),
14899            ),
14900            &[(
14901                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
14902                Hsla::red(),
14903            )]
14904        );
14905    });
14906}
14907
14908#[gpui::test]
14909async fn test_following(cx: &mut TestAppContext) {
14910    init_test(cx, |_| {});
14911
14912    let fs = FakeFs::new(cx.executor());
14913    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
14914
14915    let buffer = project.update(cx, |project, cx| {
14916        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
14917        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
14918    });
14919    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
14920    let follower = cx.update(|cx| {
14921        cx.open_window(
14922            WindowOptions {
14923                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
14924                    gpui::Point::new(px(0.), px(0.)),
14925                    gpui::Point::new(px(10.), px(80.)),
14926                ))),
14927                ..Default::default()
14928            },
14929            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
14930        )
14931        .unwrap()
14932    });
14933
14934    let is_still_following = Rc::new(RefCell::new(true));
14935    let follower_edit_event_count = Rc::new(RefCell::new(0));
14936    let pending_update = Rc::new(RefCell::new(None));
14937    let leader_entity = leader.root(cx).unwrap();
14938    let follower_entity = follower.root(cx).unwrap();
14939    _ = follower.update(cx, {
14940        let update = pending_update.clone();
14941        let is_still_following = is_still_following.clone();
14942        let follower_edit_event_count = follower_edit_event_count.clone();
14943        |_, window, cx| {
14944            cx.subscribe_in(
14945                &leader_entity,
14946                window,
14947                move |_, leader, event, window, cx| {
14948                    leader.read(cx).add_event_to_update_proto(
14949                        event,
14950                        &mut update.borrow_mut(),
14951                        window,
14952                        cx,
14953                    );
14954                },
14955            )
14956            .detach();
14957
14958            cx.subscribe_in(
14959                &follower_entity,
14960                window,
14961                move |_, _, event: &EditorEvent, _window, _cx| {
14962                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
14963                        *is_still_following.borrow_mut() = false;
14964                    }
14965
14966                    if let EditorEvent::BufferEdited = event {
14967                        *follower_edit_event_count.borrow_mut() += 1;
14968                    }
14969                },
14970            )
14971            .detach();
14972        }
14973    });
14974
14975    // Update the selections only
14976    _ = leader.update(cx, |leader, window, cx| {
14977        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14978            s.select_ranges([1..1])
14979        });
14980    });
14981    follower
14982        .update(cx, |follower, window, cx| {
14983            follower.apply_update_proto(
14984                &project,
14985                pending_update.borrow_mut().take().unwrap(),
14986                window,
14987                cx,
14988            )
14989        })
14990        .unwrap()
14991        .await
14992        .unwrap();
14993    _ = follower.update(cx, |follower, _, cx| {
14994        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
14995    });
14996    assert!(*is_still_following.borrow());
14997    assert_eq!(*follower_edit_event_count.borrow(), 0);
14998
14999    // Update the scroll position only
15000    _ = leader.update(cx, |leader, window, cx| {
15001        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
15002    });
15003    follower
15004        .update(cx, |follower, window, cx| {
15005            follower.apply_update_proto(
15006                &project,
15007                pending_update.borrow_mut().take().unwrap(),
15008                window,
15009                cx,
15010            )
15011        })
15012        .unwrap()
15013        .await
15014        .unwrap();
15015    assert_eq!(
15016        follower
15017            .update(cx, |follower, _, cx| follower.scroll_position(cx))
15018            .unwrap(),
15019        gpui::Point::new(1.5, 3.5)
15020    );
15021    assert!(*is_still_following.borrow());
15022    assert_eq!(*follower_edit_event_count.borrow(), 0);
15023
15024    // Update the selections and scroll position. The follower's scroll position is updated
15025    // via autoscroll, not via the leader's exact scroll position.
15026    _ = leader.update(cx, |leader, window, cx| {
15027        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15028            s.select_ranges([0..0])
15029        });
15030        leader.request_autoscroll(Autoscroll::newest(), cx);
15031        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
15032    });
15033    follower
15034        .update(cx, |follower, window, cx| {
15035            follower.apply_update_proto(
15036                &project,
15037                pending_update.borrow_mut().take().unwrap(),
15038                window,
15039                cx,
15040            )
15041        })
15042        .unwrap()
15043        .await
15044        .unwrap();
15045    _ = follower.update(cx, |follower, _, cx| {
15046        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
15047        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
15048    });
15049    assert!(*is_still_following.borrow());
15050
15051    // Creating a pending selection that precedes another selection
15052    _ = leader.update(cx, |leader, window, cx| {
15053        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15054            s.select_ranges([1..1])
15055        });
15056        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
15057    });
15058    follower
15059        .update(cx, |follower, window, cx| {
15060            follower.apply_update_proto(
15061                &project,
15062                pending_update.borrow_mut().take().unwrap(),
15063                window,
15064                cx,
15065            )
15066        })
15067        .unwrap()
15068        .await
15069        .unwrap();
15070    _ = follower.update(cx, |follower, _, cx| {
15071        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
15072    });
15073    assert!(*is_still_following.borrow());
15074
15075    // Extend the pending selection so that it surrounds another selection
15076    _ = leader.update(cx, |leader, window, cx| {
15077        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
15078    });
15079    follower
15080        .update(cx, |follower, window, cx| {
15081            follower.apply_update_proto(
15082                &project,
15083                pending_update.borrow_mut().take().unwrap(),
15084                window,
15085                cx,
15086            )
15087        })
15088        .unwrap()
15089        .await
15090        .unwrap();
15091    _ = follower.update(cx, |follower, _, cx| {
15092        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
15093    });
15094
15095    // Scrolling locally breaks the follow
15096    _ = follower.update(cx, |follower, window, cx| {
15097        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
15098        follower.set_scroll_anchor(
15099            ScrollAnchor {
15100                anchor: top_anchor,
15101                offset: gpui::Point::new(0.0, 0.5),
15102            },
15103            window,
15104            cx,
15105        );
15106    });
15107    assert!(!(*is_still_following.borrow()));
15108}
15109
15110#[gpui::test]
15111async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
15112    init_test(cx, |_| {});
15113
15114    let fs = FakeFs::new(cx.executor());
15115    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
15116    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15117    let pane = workspace
15118        .update(cx, |workspace, _, _| workspace.active_pane().clone())
15119        .unwrap();
15120
15121    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
15122
15123    let leader = pane.update_in(cx, |_, window, cx| {
15124        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
15125        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
15126    });
15127
15128    // Start following the editor when it has no excerpts.
15129    let mut state_message =
15130        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
15131    let workspace_entity = workspace.root(cx).unwrap();
15132    let follower_1 = cx
15133        .update_window(*workspace.deref(), |_, window, cx| {
15134            Editor::from_state_proto(
15135                workspace_entity,
15136                ViewId {
15137                    creator: CollaboratorId::PeerId(PeerId::default()),
15138                    id: 0,
15139                },
15140                &mut state_message,
15141                window,
15142                cx,
15143            )
15144        })
15145        .unwrap()
15146        .unwrap()
15147        .await
15148        .unwrap();
15149
15150    let update_message = Rc::new(RefCell::new(None));
15151    follower_1.update_in(cx, {
15152        let update = update_message.clone();
15153        |_, window, cx| {
15154            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
15155                leader.read(cx).add_event_to_update_proto(
15156                    event,
15157                    &mut update.borrow_mut(),
15158                    window,
15159                    cx,
15160                );
15161            })
15162            .detach();
15163        }
15164    });
15165
15166    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
15167        (
15168            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
15169            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
15170        )
15171    });
15172
15173    // Insert some excerpts.
15174    leader.update(cx, |leader, cx| {
15175        leader.buffer.update(cx, |multibuffer, cx| {
15176            multibuffer.set_excerpts_for_path(
15177                PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
15178                buffer_1.clone(),
15179                vec![
15180                    Point::row_range(0..3),
15181                    Point::row_range(1..6),
15182                    Point::row_range(12..15),
15183                ],
15184                0,
15185                cx,
15186            );
15187            multibuffer.set_excerpts_for_path(
15188                PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
15189                buffer_2.clone(),
15190                vec![Point::row_range(0..6), Point::row_range(8..12)],
15191                0,
15192                cx,
15193            );
15194        });
15195    });
15196
15197    // Apply the update of adding the excerpts.
15198    follower_1
15199        .update_in(cx, |follower, window, cx| {
15200            follower.apply_update_proto(
15201                &project,
15202                update_message.borrow().clone().unwrap(),
15203                window,
15204                cx,
15205            )
15206        })
15207        .await
15208        .unwrap();
15209    assert_eq!(
15210        follower_1.update(cx, |editor, cx| editor.text(cx)),
15211        leader.update(cx, |editor, cx| editor.text(cx))
15212    );
15213    update_message.borrow_mut().take();
15214
15215    // Start following separately after it already has excerpts.
15216    let mut state_message =
15217        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
15218    let workspace_entity = workspace.root(cx).unwrap();
15219    let follower_2 = cx
15220        .update_window(*workspace.deref(), |_, window, cx| {
15221            Editor::from_state_proto(
15222                workspace_entity,
15223                ViewId {
15224                    creator: CollaboratorId::PeerId(PeerId::default()),
15225                    id: 0,
15226                },
15227                &mut state_message,
15228                window,
15229                cx,
15230            )
15231        })
15232        .unwrap()
15233        .unwrap()
15234        .await
15235        .unwrap();
15236    assert_eq!(
15237        follower_2.update(cx, |editor, cx| editor.text(cx)),
15238        leader.update(cx, |editor, cx| editor.text(cx))
15239    );
15240
15241    // Remove some excerpts.
15242    leader.update(cx, |leader, cx| {
15243        leader.buffer.update(cx, |multibuffer, cx| {
15244            let excerpt_ids = multibuffer.excerpt_ids();
15245            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
15246            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
15247        });
15248    });
15249
15250    // Apply the update of removing the excerpts.
15251    follower_1
15252        .update_in(cx, |follower, window, cx| {
15253            follower.apply_update_proto(
15254                &project,
15255                update_message.borrow().clone().unwrap(),
15256                window,
15257                cx,
15258            )
15259        })
15260        .await
15261        .unwrap();
15262    follower_2
15263        .update_in(cx, |follower, window, cx| {
15264            follower.apply_update_proto(
15265                &project,
15266                update_message.borrow().clone().unwrap(),
15267                window,
15268                cx,
15269            )
15270        })
15271        .await
15272        .unwrap();
15273    update_message.borrow_mut().take();
15274    assert_eq!(
15275        follower_1.update(cx, |editor, cx| editor.text(cx)),
15276        leader.update(cx, |editor, cx| editor.text(cx))
15277    );
15278}
15279
15280#[gpui::test]
15281async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
15282    init_test(cx, |_| {});
15283
15284    let mut cx = EditorTestContext::new(cx).await;
15285    let lsp_store =
15286        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
15287
15288    cx.set_state(indoc! {"
15289        ˇfn func(abc def: i32) -> u32 {
15290        }
15291    "});
15292
15293    cx.update(|_, cx| {
15294        lsp_store.update(cx, |lsp_store, cx| {
15295            lsp_store
15296                .update_diagnostics(
15297                    LanguageServerId(0),
15298                    lsp::PublishDiagnosticsParams {
15299                        uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
15300                        version: None,
15301                        diagnostics: vec![
15302                            lsp::Diagnostic {
15303                                range: lsp::Range::new(
15304                                    lsp::Position::new(0, 11),
15305                                    lsp::Position::new(0, 12),
15306                                ),
15307                                severity: Some(lsp::DiagnosticSeverity::ERROR),
15308                                ..Default::default()
15309                            },
15310                            lsp::Diagnostic {
15311                                range: lsp::Range::new(
15312                                    lsp::Position::new(0, 12),
15313                                    lsp::Position::new(0, 15),
15314                                ),
15315                                severity: Some(lsp::DiagnosticSeverity::ERROR),
15316                                ..Default::default()
15317                            },
15318                            lsp::Diagnostic {
15319                                range: lsp::Range::new(
15320                                    lsp::Position::new(0, 25),
15321                                    lsp::Position::new(0, 28),
15322                                ),
15323                                severity: Some(lsp::DiagnosticSeverity::ERROR),
15324                                ..Default::default()
15325                            },
15326                        ],
15327                    },
15328                    None,
15329                    DiagnosticSourceKind::Pushed,
15330                    &[],
15331                    cx,
15332                )
15333                .unwrap()
15334        });
15335    });
15336
15337    executor.run_until_parked();
15338
15339    cx.update_editor(|editor, window, cx| {
15340        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
15341    });
15342
15343    cx.assert_editor_state(indoc! {"
15344        fn func(abc def: i32) -> ˇu32 {
15345        }
15346    "});
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
15376#[gpui::test]
15377async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
15378    init_test(cx, |_| {});
15379
15380    let mut cx = EditorTestContext::new(cx).await;
15381
15382    let diff_base = r#"
15383        use some::mod;
15384
15385        const A: u32 = 42;
15386
15387        fn main() {
15388            println!("hello");
15389
15390            println!("world");
15391        }
15392        "#
15393    .unindent();
15394
15395    // Edits are modified, removed, modified, added
15396    cx.set_state(
15397        &r#"
15398        use some::modified;
15399
15400        ˇ
15401        fn main() {
15402            println!("hello there");
15403
15404            println!("around the");
15405            println!("world");
15406        }
15407        "#
15408        .unindent(),
15409    );
15410
15411    cx.set_head_text(&diff_base);
15412    executor.run_until_parked();
15413
15414    cx.update_editor(|editor, window, cx| {
15415        //Wrap around the bottom of the buffer
15416        for _ in 0..3 {
15417            editor.go_to_next_hunk(&GoToHunk, window, cx);
15418        }
15419    });
15420
15421    cx.assert_editor_state(
15422        &r#"
15423        ˇuse some::modified;
15424
15425
15426        fn main() {
15427            println!("hello there");
15428
15429            println!("around the");
15430            println!("world");
15431        }
15432        "#
15433        .unindent(),
15434    );
15435
15436    cx.update_editor(|editor, window, cx| {
15437        //Wrap around the top of the buffer
15438        for _ in 0..2 {
15439            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
15440        }
15441    });
15442
15443    cx.assert_editor_state(
15444        &r#"
15445        use some::modified;
15446
15447
15448        fn main() {
15449        ˇ    println!("hello there");
15450
15451            println!("around the");
15452            println!("world");
15453        }
15454        "#
15455        .unindent(),
15456    );
15457
15458    cx.update_editor(|editor, window, cx| {
15459        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
15460    });
15461
15462    cx.assert_editor_state(
15463        &r#"
15464        use some::modified;
15465
15466        ˇ
15467        fn main() {
15468            println!("hello there");
15469
15470            println!("around the");
15471            println!("world");
15472        }
15473        "#
15474        .unindent(),
15475    );
15476
15477    cx.update_editor(|editor, window, cx| {
15478        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
15479    });
15480
15481    cx.assert_editor_state(
15482        &r#"
15483        ˇuse some::modified;
15484
15485
15486        fn main() {
15487            println!("hello there");
15488
15489            println!("around the");
15490            println!("world");
15491        }
15492        "#
15493        .unindent(),
15494    );
15495
15496    cx.update_editor(|editor, window, cx| {
15497        for _ in 0..2 {
15498            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
15499        }
15500    });
15501
15502    cx.assert_editor_state(
15503        &r#"
15504        use some::modified;
15505
15506
15507        fn main() {
15508        ˇ    println!("hello there");
15509
15510            println!("around the");
15511            println!("world");
15512        }
15513        "#
15514        .unindent(),
15515    );
15516
15517    cx.update_editor(|editor, window, cx| {
15518        editor.fold(&Fold, window, cx);
15519    });
15520
15521    cx.update_editor(|editor, window, cx| {
15522        editor.go_to_next_hunk(&GoToHunk, window, cx);
15523    });
15524
15525    cx.assert_editor_state(
15526        &r#"
15527        ˇuse some::modified;
15528
15529
15530        fn main() {
15531            println!("hello there");
15532
15533            println!("around the");
15534            println!("world");
15535        }
15536        "#
15537        .unindent(),
15538    );
15539}
15540
15541#[test]
15542fn test_split_words() {
15543    fn split(text: &str) -> Vec<&str> {
15544        split_words(text).collect()
15545    }
15546
15547    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
15548    assert_eq!(split("hello_world"), &["hello_", "world"]);
15549    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
15550    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
15551    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
15552    assert_eq!(split("helloworld"), &["helloworld"]);
15553
15554    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
15555}
15556
15557#[gpui::test]
15558async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
15559    init_test(cx, |_| {});
15560
15561    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
15562    let mut assert = |before, after| {
15563        let _state_context = cx.set_state(before);
15564        cx.run_until_parked();
15565        cx.update_editor(|editor, window, cx| {
15566            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
15567        });
15568        cx.run_until_parked();
15569        cx.assert_editor_state(after);
15570    };
15571
15572    // Outside bracket jumps to outside of matching bracket
15573    assert("console.logˇ(var);", "console.log(var)ˇ;");
15574    assert("console.log(var)ˇ;", "console.logˇ(var);");
15575
15576    // Inside bracket jumps to inside of matching bracket
15577    assert("console.log(ˇvar);", "console.log(varˇ);");
15578    assert("console.log(varˇ);", "console.log(ˇvar);");
15579
15580    // When outside a bracket and inside, favor jumping to the inside bracket
15581    assert(
15582        "console.log('foo', [1, 2, 3]ˇ);",
15583        "console.log(ˇ'foo', [1, 2, 3]);",
15584    );
15585    assert(
15586        "console.log(ˇ'foo', [1, 2, 3]);",
15587        "console.log('foo', [1, 2, 3]ˇ);",
15588    );
15589
15590    // Bias forward if two options are equally likely
15591    assert(
15592        "let result = curried_fun()ˇ();",
15593        "let result = curried_fun()()ˇ;",
15594    );
15595
15596    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
15597    assert(
15598        indoc! {"
15599            function test() {
15600                console.log('test')ˇ
15601            }"},
15602        indoc! {"
15603            function test() {
15604                console.logˇ('test')
15605            }"},
15606    );
15607}
15608
15609#[gpui::test]
15610async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
15611    init_test(cx, |_| {});
15612
15613    let fs = FakeFs::new(cx.executor());
15614    fs.insert_tree(
15615        path!("/a"),
15616        json!({
15617            "main.rs": "fn main() { let a = 5; }",
15618            "other.rs": "// Test file",
15619        }),
15620    )
15621    .await;
15622    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15623
15624    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15625    language_registry.add(Arc::new(Language::new(
15626        LanguageConfig {
15627            name: "Rust".into(),
15628            matcher: LanguageMatcher {
15629                path_suffixes: vec!["rs".to_string()],
15630                ..Default::default()
15631            },
15632            brackets: BracketPairConfig {
15633                pairs: vec![BracketPair {
15634                    start: "{".to_string(),
15635                    end: "}".to_string(),
15636                    close: true,
15637                    surround: true,
15638                    newline: true,
15639                }],
15640                disabled_scopes_by_bracket_ix: Vec::new(),
15641            },
15642            ..Default::default()
15643        },
15644        Some(tree_sitter_rust::LANGUAGE.into()),
15645    )));
15646    let mut fake_servers = language_registry.register_fake_lsp(
15647        "Rust",
15648        FakeLspAdapter {
15649            capabilities: lsp::ServerCapabilities {
15650                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
15651                    first_trigger_character: "{".to_string(),
15652                    more_trigger_character: None,
15653                }),
15654                ..Default::default()
15655            },
15656            ..Default::default()
15657        },
15658    );
15659
15660    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15661
15662    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15663
15664    let worktree_id = workspace
15665        .update(cx, |workspace, _, cx| {
15666            workspace.project().update(cx, |project, cx| {
15667                project.worktrees(cx).next().unwrap().read(cx).id()
15668            })
15669        })
15670        .unwrap();
15671
15672    let buffer = project
15673        .update(cx, |project, cx| {
15674            project.open_local_buffer(path!("/a/main.rs"), cx)
15675        })
15676        .await
15677        .unwrap();
15678    let editor_handle = workspace
15679        .update(cx, |workspace, window, cx| {
15680            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
15681        })
15682        .unwrap()
15683        .await
15684        .unwrap()
15685        .downcast::<Editor>()
15686        .unwrap();
15687
15688    cx.executor().start_waiting();
15689    let fake_server = fake_servers.next().await.unwrap();
15690
15691    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
15692        |params, _| async move {
15693            assert_eq!(
15694                params.text_document_position.text_document.uri,
15695                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
15696            );
15697            assert_eq!(
15698                params.text_document_position.position,
15699                lsp::Position::new(0, 21),
15700            );
15701
15702            Ok(Some(vec![lsp::TextEdit {
15703                new_text: "]".to_string(),
15704                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15705            }]))
15706        },
15707    );
15708
15709    editor_handle.update_in(cx, |editor, window, cx| {
15710        window.focus(&editor.focus_handle(cx));
15711        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15712            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
15713        });
15714        editor.handle_input("{", window, cx);
15715    });
15716
15717    cx.executor().run_until_parked();
15718
15719    buffer.update(cx, |buffer, _| {
15720        assert_eq!(
15721            buffer.text(),
15722            "fn main() { let a = {5}; }",
15723            "No extra braces from on type formatting should appear in the buffer"
15724        )
15725    });
15726}
15727
15728#[gpui::test(iterations = 20, seeds(31))]
15729async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
15730    init_test(cx, |_| {});
15731
15732    let mut cx = EditorLspTestContext::new_rust(
15733        lsp::ServerCapabilities {
15734            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
15735                first_trigger_character: ".".to_string(),
15736                more_trigger_character: None,
15737            }),
15738            ..Default::default()
15739        },
15740        cx,
15741    )
15742    .await;
15743
15744    cx.update_buffer(|buffer, _| {
15745        // This causes autoindent to be async.
15746        buffer.set_sync_parse_timeout(Duration::ZERO)
15747    });
15748
15749    cx.set_state("fn c() {\n    d()ˇ\n}\n");
15750    cx.simulate_keystroke("\n");
15751    cx.run_until_parked();
15752
15753    let buffer_cloned =
15754        cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap().clone());
15755    let mut request =
15756        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
15757            let buffer_cloned = buffer_cloned.clone();
15758            async move {
15759                buffer_cloned.update(&mut cx, |buffer, _| {
15760                    assert_eq!(
15761                        buffer.text(),
15762                        "fn c() {\n    d()\n        .\n}\n",
15763                        "OnTypeFormatting should triggered after autoindent applied"
15764                    )
15765                })?;
15766
15767                Ok(Some(vec![]))
15768            }
15769        });
15770
15771    cx.simulate_keystroke(".");
15772    cx.run_until_parked();
15773
15774    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
15775    assert!(request.next().await.is_some());
15776    request.close();
15777    assert!(request.next().await.is_none());
15778}
15779
15780#[gpui::test]
15781async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
15782    init_test(cx, |_| {});
15783
15784    let fs = FakeFs::new(cx.executor());
15785    fs.insert_tree(
15786        path!("/a"),
15787        json!({
15788            "main.rs": "fn main() { let a = 5; }",
15789            "other.rs": "// Test file",
15790        }),
15791    )
15792    .await;
15793
15794    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15795
15796    let server_restarts = Arc::new(AtomicUsize::new(0));
15797    let closure_restarts = Arc::clone(&server_restarts);
15798    let language_server_name = "test language server";
15799    let language_name: LanguageName = "Rust".into();
15800
15801    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15802    language_registry.add(Arc::new(Language::new(
15803        LanguageConfig {
15804            name: language_name.clone(),
15805            matcher: LanguageMatcher {
15806                path_suffixes: vec!["rs".to_string()],
15807                ..Default::default()
15808            },
15809            ..Default::default()
15810        },
15811        Some(tree_sitter_rust::LANGUAGE.into()),
15812    )));
15813    let mut fake_servers = language_registry.register_fake_lsp(
15814        "Rust",
15815        FakeLspAdapter {
15816            name: language_server_name,
15817            initialization_options: Some(json!({
15818                "testOptionValue": true
15819            })),
15820            initializer: Some(Box::new(move |fake_server| {
15821                let task_restarts = Arc::clone(&closure_restarts);
15822                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
15823                    task_restarts.fetch_add(1, atomic::Ordering::Release);
15824                    futures::future::ready(Ok(()))
15825                });
15826            })),
15827            ..Default::default()
15828        },
15829    );
15830
15831    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15832    let _buffer = project
15833        .update(cx, |project, cx| {
15834            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
15835        })
15836        .await
15837        .unwrap();
15838    let _fake_server = fake_servers.next().await.unwrap();
15839    update_test_language_settings(cx, |language_settings| {
15840        language_settings.languages.0.insert(
15841            language_name.clone(),
15842            LanguageSettingsContent {
15843                tab_size: NonZeroU32::new(8),
15844                ..Default::default()
15845            },
15846        );
15847    });
15848    cx.executor().run_until_parked();
15849    assert_eq!(
15850        server_restarts.load(atomic::Ordering::Acquire),
15851        0,
15852        "Should not restart LSP server on an unrelated change"
15853    );
15854
15855    update_test_project_settings(cx, |project_settings| {
15856        project_settings.lsp.insert(
15857            "Some other server name".into(),
15858            LspSettings {
15859                binary: None,
15860                settings: None,
15861                initialization_options: Some(json!({
15862                    "some other init value": false
15863                })),
15864                enable_lsp_tasks: false,
15865            },
15866        );
15867    });
15868    cx.executor().run_until_parked();
15869    assert_eq!(
15870        server_restarts.load(atomic::Ordering::Acquire),
15871        0,
15872        "Should not restart LSP server on an unrelated LSP settings change"
15873    );
15874
15875    update_test_project_settings(cx, |project_settings| {
15876        project_settings.lsp.insert(
15877            language_server_name.into(),
15878            LspSettings {
15879                binary: None,
15880                settings: None,
15881                initialization_options: Some(json!({
15882                    "anotherInitValue": false
15883                })),
15884                enable_lsp_tasks: false,
15885            },
15886        );
15887    });
15888    cx.executor().run_until_parked();
15889    assert_eq!(
15890        server_restarts.load(atomic::Ordering::Acquire),
15891        1,
15892        "Should restart LSP server on a related LSP settings change"
15893    );
15894
15895    update_test_project_settings(cx, |project_settings| {
15896        project_settings.lsp.insert(
15897            language_server_name.into(),
15898            LspSettings {
15899                binary: None,
15900                settings: None,
15901                initialization_options: Some(json!({
15902                    "anotherInitValue": false
15903                })),
15904                enable_lsp_tasks: false,
15905            },
15906        );
15907    });
15908    cx.executor().run_until_parked();
15909    assert_eq!(
15910        server_restarts.load(atomic::Ordering::Acquire),
15911        1,
15912        "Should not restart LSP server on a related LSP settings change that is the same"
15913    );
15914
15915    update_test_project_settings(cx, |project_settings| {
15916        project_settings.lsp.insert(
15917            language_server_name.into(),
15918            LspSettings {
15919                binary: None,
15920                settings: None,
15921                initialization_options: None,
15922                enable_lsp_tasks: false,
15923            },
15924        );
15925    });
15926    cx.executor().run_until_parked();
15927    assert_eq!(
15928        server_restarts.load(atomic::Ordering::Acquire),
15929        2,
15930        "Should restart LSP server on another related LSP settings change"
15931    );
15932}
15933
15934#[gpui::test]
15935async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
15936    init_test(cx, |_| {});
15937
15938    let mut cx = EditorLspTestContext::new_rust(
15939        lsp::ServerCapabilities {
15940            completion_provider: Some(lsp::CompletionOptions {
15941                trigger_characters: Some(vec![".".to_string()]),
15942                resolve_provider: Some(true),
15943                ..Default::default()
15944            }),
15945            ..Default::default()
15946        },
15947        cx,
15948    )
15949    .await;
15950
15951    cx.set_state("fn main() { let a = 2ˇ; }");
15952    cx.simulate_keystroke(".");
15953    let completion_item = lsp::CompletionItem {
15954        label: "some".into(),
15955        kind: Some(lsp::CompletionItemKind::SNIPPET),
15956        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15957        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15958            kind: lsp::MarkupKind::Markdown,
15959            value: "```rust\nSome(2)\n```".to_string(),
15960        })),
15961        deprecated: Some(false),
15962        sort_text: Some("fffffff2".to_string()),
15963        filter_text: Some("some".to_string()),
15964        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15965        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15966            range: lsp::Range {
15967                start: lsp::Position {
15968                    line: 0,
15969                    character: 22,
15970                },
15971                end: lsp::Position {
15972                    line: 0,
15973                    character: 22,
15974                },
15975            },
15976            new_text: "Some(2)".to_string(),
15977        })),
15978        additional_text_edits: Some(vec![lsp::TextEdit {
15979            range: lsp::Range {
15980                start: lsp::Position {
15981                    line: 0,
15982                    character: 20,
15983                },
15984                end: lsp::Position {
15985                    line: 0,
15986                    character: 22,
15987                },
15988            },
15989            new_text: "".to_string(),
15990        }]),
15991        ..Default::default()
15992    };
15993
15994    let closure_completion_item = completion_item.clone();
15995    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15996        let task_completion_item = closure_completion_item.clone();
15997        async move {
15998            Ok(Some(lsp::CompletionResponse::Array(vec![
15999                task_completion_item,
16000            ])))
16001        }
16002    });
16003
16004    request.next().await;
16005
16006    cx.condition(|editor, _| editor.context_menu_visible())
16007        .await;
16008    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
16009        editor
16010            .confirm_completion(&ConfirmCompletion::default(), window, cx)
16011            .unwrap()
16012    });
16013    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
16014
16015    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
16016        let task_completion_item = completion_item.clone();
16017        async move { Ok(task_completion_item) }
16018    })
16019    .next()
16020    .await
16021    .unwrap();
16022    apply_additional_edits.await.unwrap();
16023    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
16024}
16025
16026#[gpui::test]
16027async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
16028    init_test(cx, |_| {});
16029
16030    let mut cx = EditorLspTestContext::new_rust(
16031        lsp::ServerCapabilities {
16032            completion_provider: Some(lsp::CompletionOptions {
16033                trigger_characters: Some(vec![".".to_string()]),
16034                resolve_provider: Some(true),
16035                ..Default::default()
16036            }),
16037            ..Default::default()
16038        },
16039        cx,
16040    )
16041    .await;
16042
16043    cx.set_state("fn main() { let a = 2ˇ; }");
16044    cx.simulate_keystroke(".");
16045
16046    let item1 = lsp::CompletionItem {
16047        label: "method id()".to_string(),
16048        filter_text: Some("id".to_string()),
16049        detail: None,
16050        documentation: None,
16051        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16052            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16053            new_text: ".id".to_string(),
16054        })),
16055        ..lsp::CompletionItem::default()
16056    };
16057
16058    let item2 = lsp::CompletionItem {
16059        label: "other".to_string(),
16060        filter_text: Some("other".to_string()),
16061        detail: None,
16062        documentation: None,
16063        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16064            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16065            new_text: ".other".to_string(),
16066        })),
16067        ..lsp::CompletionItem::default()
16068    };
16069
16070    let item1 = item1.clone();
16071    cx.set_request_handler::<lsp::request::Completion, _, _>({
16072        let item1 = item1.clone();
16073        move |_, _, _| {
16074            let item1 = item1.clone();
16075            let item2 = item2.clone();
16076            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
16077        }
16078    })
16079    .next()
16080    .await;
16081
16082    cx.condition(|editor, _| editor.context_menu_visible())
16083        .await;
16084    cx.update_editor(|editor, _, _| {
16085        let context_menu = editor.context_menu.borrow_mut();
16086        let context_menu = context_menu
16087            .as_ref()
16088            .expect("Should have the context menu deployed");
16089        match context_menu {
16090            CodeContextMenu::Completions(completions_menu) => {
16091                let completions = completions_menu.completions.borrow_mut();
16092                assert_eq!(
16093                    completions
16094                        .iter()
16095                        .map(|completion| &completion.label.text)
16096                        .collect::<Vec<_>>(),
16097                    vec!["method id()", "other"]
16098                )
16099            }
16100            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
16101        }
16102    });
16103
16104    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
16105        let item1 = item1.clone();
16106        move |_, item_to_resolve, _| {
16107            let item1 = item1.clone();
16108            async move {
16109                if item1 == item_to_resolve {
16110                    Ok(lsp::CompletionItem {
16111                        label: "method id()".to_string(),
16112                        filter_text: Some("id".to_string()),
16113                        detail: Some("Now resolved!".to_string()),
16114                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
16115                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16116                            range: lsp::Range::new(
16117                                lsp::Position::new(0, 22),
16118                                lsp::Position::new(0, 22),
16119                            ),
16120                            new_text: ".id".to_string(),
16121                        })),
16122                        ..lsp::CompletionItem::default()
16123                    })
16124                } else {
16125                    Ok(item_to_resolve)
16126                }
16127            }
16128        }
16129    })
16130    .next()
16131    .await
16132    .unwrap();
16133    cx.run_until_parked();
16134
16135    cx.update_editor(|editor, window, cx| {
16136        editor.context_menu_next(&Default::default(), window, cx);
16137    });
16138
16139    cx.update_editor(|editor, _, _| {
16140        let context_menu = editor.context_menu.borrow_mut();
16141        let context_menu = context_menu
16142            .as_ref()
16143            .expect("Should have the context menu deployed");
16144        match context_menu {
16145            CodeContextMenu::Completions(completions_menu) => {
16146                let completions = completions_menu.completions.borrow_mut();
16147                assert_eq!(
16148                    completions
16149                        .iter()
16150                        .map(|completion| &completion.label.text)
16151                        .collect::<Vec<_>>(),
16152                    vec!["method id() Now resolved!", "other"],
16153                    "Should update first completion label, but not second as the filter text did not match."
16154                );
16155            }
16156            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
16157        }
16158    });
16159}
16160
16161#[gpui::test]
16162async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
16163    init_test(cx, |_| {});
16164    let mut cx = EditorLspTestContext::new_rust(
16165        lsp::ServerCapabilities {
16166            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
16167            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
16168            completion_provider: Some(lsp::CompletionOptions {
16169                resolve_provider: Some(true),
16170                ..Default::default()
16171            }),
16172            ..Default::default()
16173        },
16174        cx,
16175    )
16176    .await;
16177    cx.set_state(indoc! {"
16178        struct TestStruct {
16179            field: i32
16180        }
16181
16182        fn mainˇ() {
16183            let unused_var = 42;
16184            let test_struct = TestStruct { field: 42 };
16185        }
16186    "});
16187    let symbol_range = cx.lsp_range(indoc! {"
16188        struct TestStruct {
16189            field: i32
16190        }
16191
16192        «fn main»() {
16193            let unused_var = 42;
16194            let test_struct = TestStruct { field: 42 };
16195        }
16196    "});
16197    let mut hover_requests =
16198        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
16199            Ok(Some(lsp::Hover {
16200                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
16201                    kind: lsp::MarkupKind::Markdown,
16202                    value: "Function documentation".to_string(),
16203                }),
16204                range: Some(symbol_range),
16205            }))
16206        });
16207
16208    // Case 1: Test that code action menu hide hover popover
16209    cx.dispatch_action(Hover);
16210    hover_requests.next().await;
16211    cx.condition(|editor, _| editor.hover_state.visible()).await;
16212    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
16213        move |_, _, _| async move {
16214            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
16215                lsp::CodeAction {
16216                    title: "Remove unused variable".to_string(),
16217                    kind: Some(CodeActionKind::QUICKFIX),
16218                    edit: Some(lsp::WorkspaceEdit {
16219                        changes: Some(
16220                            [(
16221                                lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
16222                                vec![lsp::TextEdit {
16223                                    range: lsp::Range::new(
16224                                        lsp::Position::new(5, 4),
16225                                        lsp::Position::new(5, 27),
16226                                    ),
16227                                    new_text: "".to_string(),
16228                                }],
16229                            )]
16230                            .into_iter()
16231                            .collect(),
16232                        ),
16233                        ..Default::default()
16234                    }),
16235                    ..Default::default()
16236                },
16237            )]))
16238        },
16239    );
16240    cx.update_editor(|editor, window, cx| {
16241        editor.toggle_code_actions(
16242            &ToggleCodeActions {
16243                deployed_from: None,
16244                quick_launch: false,
16245            },
16246            window,
16247            cx,
16248        );
16249    });
16250    code_action_requests.next().await;
16251    cx.run_until_parked();
16252    cx.condition(|editor, _| editor.context_menu_visible())
16253        .await;
16254    cx.update_editor(|editor, _, _| {
16255        assert!(
16256            !editor.hover_state.visible(),
16257            "Hover popover should be hidden when code action menu is shown"
16258        );
16259        // Hide code actions
16260        editor.context_menu.take();
16261    });
16262
16263    // Case 2: Test that code completions hide hover popover
16264    cx.dispatch_action(Hover);
16265    hover_requests.next().await;
16266    cx.condition(|editor, _| editor.hover_state.visible()).await;
16267    let counter = Arc::new(AtomicUsize::new(0));
16268    let mut completion_requests =
16269        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16270            let counter = counter.clone();
16271            async move {
16272                counter.fetch_add(1, atomic::Ordering::Release);
16273                Ok(Some(lsp::CompletionResponse::Array(vec![
16274                    lsp::CompletionItem {
16275                        label: "main".into(),
16276                        kind: Some(lsp::CompletionItemKind::FUNCTION),
16277                        detail: Some("() -> ()".to_string()),
16278                        ..Default::default()
16279                    },
16280                    lsp::CompletionItem {
16281                        label: "TestStruct".into(),
16282                        kind: Some(lsp::CompletionItemKind::STRUCT),
16283                        detail: Some("struct TestStruct".to_string()),
16284                        ..Default::default()
16285                    },
16286                ])))
16287            }
16288        });
16289    cx.update_editor(|editor, window, cx| {
16290        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
16291    });
16292    completion_requests.next().await;
16293    cx.condition(|editor, _| editor.context_menu_visible())
16294        .await;
16295    cx.update_editor(|editor, _, _| {
16296        assert!(
16297            !editor.hover_state.visible(),
16298            "Hover popover should be hidden when completion menu is shown"
16299        );
16300    });
16301}
16302
16303#[gpui::test]
16304async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
16305    init_test(cx, |_| {});
16306
16307    let mut cx = EditorLspTestContext::new_rust(
16308        lsp::ServerCapabilities {
16309            completion_provider: Some(lsp::CompletionOptions {
16310                trigger_characters: Some(vec![".".to_string()]),
16311                resolve_provider: Some(true),
16312                ..Default::default()
16313            }),
16314            ..Default::default()
16315        },
16316        cx,
16317    )
16318    .await;
16319
16320    cx.set_state("fn main() { let a = 2ˇ; }");
16321    cx.simulate_keystroke(".");
16322
16323    let unresolved_item_1 = lsp::CompletionItem {
16324        label: "id".to_string(),
16325        filter_text: Some("id".to_string()),
16326        detail: None,
16327        documentation: None,
16328        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16329            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16330            new_text: ".id".to_string(),
16331        })),
16332        ..lsp::CompletionItem::default()
16333    };
16334    let resolved_item_1 = lsp::CompletionItem {
16335        additional_text_edits: Some(vec![lsp::TextEdit {
16336            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
16337            new_text: "!!".to_string(),
16338        }]),
16339        ..unresolved_item_1.clone()
16340    };
16341    let unresolved_item_2 = lsp::CompletionItem {
16342        label: "other".to_string(),
16343        filter_text: Some("other".to_string()),
16344        detail: None,
16345        documentation: None,
16346        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16347            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16348            new_text: ".other".to_string(),
16349        })),
16350        ..lsp::CompletionItem::default()
16351    };
16352    let resolved_item_2 = lsp::CompletionItem {
16353        additional_text_edits: Some(vec![lsp::TextEdit {
16354            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
16355            new_text: "??".to_string(),
16356        }]),
16357        ..unresolved_item_2.clone()
16358    };
16359
16360    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
16361    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
16362    cx.lsp
16363        .server
16364        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
16365            let unresolved_item_1 = unresolved_item_1.clone();
16366            let resolved_item_1 = resolved_item_1.clone();
16367            let unresolved_item_2 = unresolved_item_2.clone();
16368            let resolved_item_2 = resolved_item_2.clone();
16369            let resolve_requests_1 = resolve_requests_1.clone();
16370            let resolve_requests_2 = resolve_requests_2.clone();
16371            move |unresolved_request, _| {
16372                let unresolved_item_1 = unresolved_item_1.clone();
16373                let resolved_item_1 = resolved_item_1.clone();
16374                let unresolved_item_2 = unresolved_item_2.clone();
16375                let resolved_item_2 = resolved_item_2.clone();
16376                let resolve_requests_1 = resolve_requests_1.clone();
16377                let resolve_requests_2 = resolve_requests_2.clone();
16378                async move {
16379                    if unresolved_request == unresolved_item_1 {
16380                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
16381                        Ok(resolved_item_1.clone())
16382                    } else if unresolved_request == unresolved_item_2 {
16383                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
16384                        Ok(resolved_item_2.clone())
16385                    } else {
16386                        panic!("Unexpected completion item {unresolved_request:?}")
16387                    }
16388                }
16389            }
16390        })
16391        .detach();
16392
16393    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16394        let unresolved_item_1 = unresolved_item_1.clone();
16395        let unresolved_item_2 = unresolved_item_2.clone();
16396        async move {
16397            Ok(Some(lsp::CompletionResponse::Array(vec![
16398                unresolved_item_1,
16399                unresolved_item_2,
16400            ])))
16401        }
16402    })
16403    .next()
16404    .await;
16405
16406    cx.condition(|editor, _| editor.context_menu_visible())
16407        .await;
16408    cx.update_editor(|editor, _, _| {
16409        let context_menu = editor.context_menu.borrow_mut();
16410        let context_menu = context_menu
16411            .as_ref()
16412            .expect("Should have the context menu deployed");
16413        match context_menu {
16414            CodeContextMenu::Completions(completions_menu) => {
16415                let completions = completions_menu.completions.borrow_mut();
16416                assert_eq!(
16417                    completions
16418                        .iter()
16419                        .map(|completion| &completion.label.text)
16420                        .collect::<Vec<_>>(),
16421                    vec!["id", "other"]
16422                )
16423            }
16424            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
16425        }
16426    });
16427    cx.run_until_parked();
16428
16429    cx.update_editor(|editor, window, cx| {
16430        editor.context_menu_next(&ContextMenuNext, window, cx);
16431    });
16432    cx.run_until_parked();
16433    cx.update_editor(|editor, window, cx| {
16434        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
16435    });
16436    cx.run_until_parked();
16437    cx.update_editor(|editor, window, cx| {
16438        editor.context_menu_next(&ContextMenuNext, window, cx);
16439    });
16440    cx.run_until_parked();
16441    cx.update_editor(|editor, window, cx| {
16442        editor
16443            .compose_completion(&ComposeCompletion::default(), window, cx)
16444            .expect("No task returned")
16445    })
16446    .await
16447    .expect("Completion failed");
16448    cx.run_until_parked();
16449
16450    cx.update_editor(|editor, _, cx| {
16451        assert_eq!(
16452            resolve_requests_1.load(atomic::Ordering::Acquire),
16453            1,
16454            "Should always resolve once despite multiple selections"
16455        );
16456        assert_eq!(
16457            resolve_requests_2.load(atomic::Ordering::Acquire),
16458            1,
16459            "Should always resolve once after multiple selections and applying the completion"
16460        );
16461        assert_eq!(
16462            editor.text(cx),
16463            "fn main() { let a = ??.other; }",
16464            "Should use resolved data when applying the completion"
16465        );
16466    });
16467}
16468
16469#[gpui::test]
16470async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
16471    init_test(cx, |_| {});
16472
16473    let item_0 = lsp::CompletionItem {
16474        label: "abs".into(),
16475        insert_text: Some("abs".into()),
16476        data: Some(json!({ "very": "special"})),
16477        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
16478        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
16479            lsp::InsertReplaceEdit {
16480                new_text: "abs".to_string(),
16481                insert: lsp::Range::default(),
16482                replace: lsp::Range::default(),
16483            },
16484        )),
16485        ..lsp::CompletionItem::default()
16486    };
16487    let items = iter::once(item_0.clone())
16488        .chain((11..51).map(|i| lsp::CompletionItem {
16489            label: format!("item_{}", i),
16490            insert_text: Some(format!("item_{}", i)),
16491            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
16492            ..lsp::CompletionItem::default()
16493        }))
16494        .collect::<Vec<_>>();
16495
16496    let default_commit_characters = vec!["?".to_string()];
16497    let default_data = json!({ "default": "data"});
16498    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
16499    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
16500    let default_edit_range = lsp::Range {
16501        start: lsp::Position {
16502            line: 0,
16503            character: 5,
16504        },
16505        end: lsp::Position {
16506            line: 0,
16507            character: 5,
16508        },
16509    };
16510
16511    let mut cx = EditorLspTestContext::new_rust(
16512        lsp::ServerCapabilities {
16513            completion_provider: Some(lsp::CompletionOptions {
16514                trigger_characters: Some(vec![".".to_string()]),
16515                resolve_provider: Some(true),
16516                ..Default::default()
16517            }),
16518            ..Default::default()
16519        },
16520        cx,
16521    )
16522    .await;
16523
16524    cx.set_state("fn main() { let a = 2ˇ; }");
16525    cx.simulate_keystroke(".");
16526
16527    let completion_data = default_data.clone();
16528    let completion_characters = default_commit_characters.clone();
16529    let completion_items = items.clone();
16530    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16531        let default_data = completion_data.clone();
16532        let default_commit_characters = completion_characters.clone();
16533        let items = completion_items.clone();
16534        async move {
16535            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16536                items,
16537                item_defaults: Some(lsp::CompletionListItemDefaults {
16538                    data: Some(default_data.clone()),
16539                    commit_characters: Some(default_commit_characters.clone()),
16540                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
16541                        default_edit_range,
16542                    )),
16543                    insert_text_format: Some(default_insert_text_format),
16544                    insert_text_mode: Some(default_insert_text_mode),
16545                }),
16546                ..lsp::CompletionList::default()
16547            })))
16548        }
16549    })
16550    .next()
16551    .await;
16552
16553    let resolved_items = Arc::new(Mutex::new(Vec::new()));
16554    cx.lsp
16555        .server
16556        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
16557            let closure_resolved_items = resolved_items.clone();
16558            move |item_to_resolve, _| {
16559                let closure_resolved_items = closure_resolved_items.clone();
16560                async move {
16561                    closure_resolved_items.lock().push(item_to_resolve.clone());
16562                    Ok(item_to_resolve)
16563                }
16564            }
16565        })
16566        .detach();
16567
16568    cx.condition(|editor, _| editor.context_menu_visible())
16569        .await;
16570    cx.run_until_parked();
16571    cx.update_editor(|editor, _, _| {
16572        let menu = editor.context_menu.borrow_mut();
16573        match menu.as_ref().expect("should have the completions menu") {
16574            CodeContextMenu::Completions(completions_menu) => {
16575                assert_eq!(
16576                    completions_menu
16577                        .entries
16578                        .borrow()
16579                        .iter()
16580                        .map(|mat| mat.string.clone())
16581                        .collect::<Vec<String>>(),
16582                    items
16583                        .iter()
16584                        .map(|completion| completion.label.clone())
16585                        .collect::<Vec<String>>()
16586                );
16587            }
16588            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
16589        }
16590    });
16591    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
16592    // with 4 from the end.
16593    assert_eq!(
16594        *resolved_items.lock(),
16595        [&items[0..16], &items[items.len() - 4..items.len()]]
16596            .concat()
16597            .iter()
16598            .cloned()
16599            .map(|mut item| {
16600                if item.data.is_none() {
16601                    item.data = Some(default_data.clone());
16602                }
16603                item
16604            })
16605            .collect::<Vec<lsp::CompletionItem>>(),
16606        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
16607    );
16608    resolved_items.lock().clear();
16609
16610    cx.update_editor(|editor, window, cx| {
16611        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
16612    });
16613    cx.run_until_parked();
16614    // Completions that have already been resolved are skipped.
16615    assert_eq!(
16616        *resolved_items.lock(),
16617        items[items.len() - 17..items.len() - 4]
16618            .iter()
16619            .cloned()
16620            .map(|mut item| {
16621                if item.data.is_none() {
16622                    item.data = Some(default_data.clone());
16623                }
16624                item
16625            })
16626            .collect::<Vec<lsp::CompletionItem>>()
16627    );
16628    resolved_items.lock().clear();
16629}
16630
16631#[gpui::test]
16632async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
16633    init_test(cx, |_| {});
16634
16635    let mut cx = EditorLspTestContext::new(
16636        Language::new(
16637            LanguageConfig {
16638                matcher: LanguageMatcher {
16639                    path_suffixes: vec!["jsx".into()],
16640                    ..Default::default()
16641                },
16642                overrides: [(
16643                    "element".into(),
16644                    LanguageConfigOverride {
16645                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
16646                        ..Default::default()
16647                    },
16648                )]
16649                .into_iter()
16650                .collect(),
16651                ..Default::default()
16652            },
16653            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
16654        )
16655        .with_override_query("(jsx_self_closing_element) @element")
16656        .unwrap(),
16657        lsp::ServerCapabilities {
16658            completion_provider: Some(lsp::CompletionOptions {
16659                trigger_characters: Some(vec![":".to_string()]),
16660                ..Default::default()
16661            }),
16662            ..Default::default()
16663        },
16664        cx,
16665    )
16666    .await;
16667
16668    cx.lsp
16669        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16670            Ok(Some(lsp::CompletionResponse::Array(vec![
16671                lsp::CompletionItem {
16672                    label: "bg-blue".into(),
16673                    ..Default::default()
16674                },
16675                lsp::CompletionItem {
16676                    label: "bg-red".into(),
16677                    ..Default::default()
16678                },
16679                lsp::CompletionItem {
16680                    label: "bg-yellow".into(),
16681                    ..Default::default()
16682                },
16683            ])))
16684        });
16685
16686    cx.set_state(r#"<p class="bgˇ" />"#);
16687
16688    // Trigger completion when typing a dash, because the dash is an extra
16689    // word character in the 'element' scope, which contains the cursor.
16690    cx.simulate_keystroke("-");
16691    cx.executor().run_until_parked();
16692    cx.update_editor(|editor, _, _| {
16693        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16694        {
16695            assert_eq!(
16696                completion_menu_entries(menu),
16697                &["bg-blue", "bg-red", "bg-yellow"]
16698            );
16699        } else {
16700            panic!("expected completion menu to be open");
16701        }
16702    });
16703
16704    cx.simulate_keystroke("l");
16705    cx.executor().run_until_parked();
16706    cx.update_editor(|editor, _, _| {
16707        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16708        {
16709            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
16710        } else {
16711            panic!("expected completion menu to be open");
16712        }
16713    });
16714
16715    // When filtering completions, consider the character after the '-' to
16716    // be the start of a subword.
16717    cx.set_state(r#"<p class="yelˇ" />"#);
16718    cx.simulate_keystroke("l");
16719    cx.executor().run_until_parked();
16720    cx.update_editor(|editor, _, _| {
16721        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16722        {
16723            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
16724        } else {
16725            panic!("expected completion menu to be open");
16726        }
16727    });
16728}
16729
16730fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
16731    let entries = menu.entries.borrow();
16732    entries.iter().map(|mat| mat.string.clone()).collect()
16733}
16734
16735#[gpui::test]
16736async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
16737    init_test(cx, |settings| {
16738        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
16739            Formatter::Prettier,
16740        )))
16741    });
16742
16743    let fs = FakeFs::new(cx.executor());
16744    fs.insert_file(path!("/file.ts"), Default::default()).await;
16745
16746    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
16747    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16748
16749    language_registry.add(Arc::new(Language::new(
16750        LanguageConfig {
16751            name: "TypeScript".into(),
16752            matcher: LanguageMatcher {
16753                path_suffixes: vec!["ts".to_string()],
16754                ..Default::default()
16755            },
16756            ..Default::default()
16757        },
16758        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
16759    )));
16760    update_test_language_settings(cx, |settings| {
16761        settings.defaults.prettier = Some(PrettierSettings {
16762            allowed: true,
16763            ..PrettierSettings::default()
16764        });
16765    });
16766
16767    let test_plugin = "test_plugin";
16768    let _ = language_registry.register_fake_lsp(
16769        "TypeScript",
16770        FakeLspAdapter {
16771            prettier_plugins: vec![test_plugin],
16772            ..Default::default()
16773        },
16774    );
16775
16776    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
16777    let buffer = project
16778        .update(cx, |project, cx| {
16779            project.open_local_buffer(path!("/file.ts"), cx)
16780        })
16781        .await
16782        .unwrap();
16783
16784    let buffer_text = "one\ntwo\nthree\n";
16785    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16786    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16787    editor.update_in(cx, |editor, window, cx| {
16788        editor.set_text(buffer_text, window, cx)
16789    });
16790
16791    editor
16792        .update_in(cx, |editor, window, cx| {
16793            editor.perform_format(
16794                project.clone(),
16795                FormatTrigger::Manual,
16796                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
16797                window,
16798                cx,
16799            )
16800        })
16801        .unwrap()
16802        .await;
16803    assert_eq!(
16804        editor.update(cx, |editor, cx| editor.text(cx)),
16805        buffer_text.to_string() + prettier_format_suffix,
16806        "Test prettier formatting was not applied to the original buffer text",
16807    );
16808
16809    update_test_language_settings(cx, |settings| {
16810        settings.defaults.formatter = Some(SelectedFormatter::Auto)
16811    });
16812    let format = editor.update_in(cx, |editor, window, cx| {
16813        editor.perform_format(
16814            project.clone(),
16815            FormatTrigger::Manual,
16816            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
16817            window,
16818            cx,
16819        )
16820    });
16821    format.await.unwrap();
16822    assert_eq!(
16823        editor.update(cx, |editor, cx| editor.text(cx)),
16824        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
16825        "Autoformatting (via test prettier) was not applied to the original buffer text",
16826    );
16827}
16828
16829#[gpui::test]
16830async fn test_addition_reverts(cx: &mut TestAppContext) {
16831    init_test(cx, |_| {});
16832    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16833    let base_text = indoc! {r#"
16834        struct Row;
16835        struct Row1;
16836        struct Row2;
16837
16838        struct Row4;
16839        struct Row5;
16840        struct Row6;
16841
16842        struct Row8;
16843        struct Row9;
16844        struct Row10;"#};
16845
16846    // When addition hunks are not adjacent to carets, no hunk revert is performed
16847    assert_hunk_revert(
16848        indoc! {r#"struct Row;
16849                   struct Row1;
16850                   struct Row1.1;
16851                   struct Row1.2;
16852                   struct Row2;ˇ
16853
16854                   struct Row4;
16855                   struct Row5;
16856                   struct Row6;
16857
16858                   struct Row8;
16859                   ˇstruct Row9;
16860                   struct Row9.1;
16861                   struct Row9.2;
16862                   struct Row9.3;
16863                   struct Row10;"#},
16864        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
16865        indoc! {r#"struct Row;
16866                   struct Row1;
16867                   struct Row1.1;
16868                   struct Row1.2;
16869                   struct Row2;ˇ
16870
16871                   struct Row4;
16872                   struct Row5;
16873                   struct Row6;
16874
16875                   struct Row8;
16876                   ˇstruct Row9;
16877                   struct Row9.1;
16878                   struct Row9.2;
16879                   struct Row9.3;
16880                   struct Row10;"#},
16881        base_text,
16882        &mut cx,
16883    );
16884    // Same for selections
16885    assert_hunk_revert(
16886        indoc! {r#"struct Row;
16887                   struct Row1;
16888                   struct Row2;
16889                   struct Row2.1;
16890                   struct Row2.2;
16891                   «ˇ
16892                   struct Row4;
16893                   struct» Row5;
16894                   «struct Row6;
16895                   ˇ»
16896                   struct Row9.1;
16897                   struct Row9.2;
16898                   struct Row9.3;
16899                   struct Row8;
16900                   struct Row9;
16901                   struct Row10;"#},
16902        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
16903        indoc! {r#"struct Row;
16904                   struct Row1;
16905                   struct Row2;
16906                   struct Row2.1;
16907                   struct Row2.2;
16908                   «ˇ
16909                   struct Row4;
16910                   struct» Row5;
16911                   «struct Row6;
16912                   ˇ»
16913                   struct Row9.1;
16914                   struct Row9.2;
16915                   struct Row9.3;
16916                   struct Row8;
16917                   struct Row9;
16918                   struct Row10;"#},
16919        base_text,
16920        &mut cx,
16921    );
16922
16923    // When carets and selections intersect the addition hunks, those are reverted.
16924    // Adjacent carets got merged.
16925    assert_hunk_revert(
16926        indoc! {r#"struct Row;
16927                   ˇ// something on the top
16928                   struct Row1;
16929                   struct Row2;
16930                   struct Roˇw3.1;
16931                   struct Row2.2;
16932                   struct Row2.3;ˇ
16933
16934                   struct Row4;
16935                   struct ˇRow5.1;
16936                   struct Row5.2;
16937                   struct «Rowˇ»5.3;
16938                   struct Row5;
16939                   struct Row6;
16940                   ˇ
16941                   struct Row9.1;
16942                   struct «Rowˇ»9.2;
16943                   struct «ˇRow»9.3;
16944                   struct Row8;
16945                   struct Row9;
16946                   «ˇ// something on bottom»
16947                   struct Row10;"#},
16948        vec![
16949            DiffHunkStatusKind::Added,
16950            DiffHunkStatusKind::Added,
16951            DiffHunkStatusKind::Added,
16952            DiffHunkStatusKind::Added,
16953            DiffHunkStatusKind::Added,
16954        ],
16955        indoc! {r#"struct Row;
16956                   ˇstruct Row1;
16957                   struct Row2;
16958                   ˇ
16959                   struct Row4;
16960                   ˇstruct Row5;
16961                   struct Row6;
16962                   ˇ
16963                   ˇstruct Row8;
16964                   struct Row9;
16965                   ˇstruct Row10;"#},
16966        base_text,
16967        &mut cx,
16968    );
16969}
16970
16971#[gpui::test]
16972async fn test_modification_reverts(cx: &mut TestAppContext) {
16973    init_test(cx, |_| {});
16974    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16975    let base_text = indoc! {r#"
16976        struct Row;
16977        struct Row1;
16978        struct Row2;
16979
16980        struct Row4;
16981        struct Row5;
16982        struct Row6;
16983
16984        struct Row8;
16985        struct Row9;
16986        struct Row10;"#};
16987
16988    // Modification hunks behave the same as the addition ones.
16989    assert_hunk_revert(
16990        indoc! {r#"struct Row;
16991                   struct Row1;
16992                   struct Row33;
16993                   ˇ
16994                   struct Row4;
16995                   struct Row5;
16996                   struct Row6;
16997                   ˇ
16998                   struct Row99;
16999                   struct Row9;
17000                   struct Row10;"#},
17001        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
17002        indoc! {r#"struct Row;
17003                   struct Row1;
17004                   struct Row33;
17005                   ˇ
17006                   struct Row4;
17007                   struct Row5;
17008                   struct Row6;
17009                   ˇ
17010                   struct Row99;
17011                   struct Row9;
17012                   struct Row10;"#},
17013        base_text,
17014        &mut cx,
17015    );
17016    assert_hunk_revert(
17017        indoc! {r#"struct Row;
17018                   struct Row1;
17019                   struct Row33;
17020                   «ˇ
17021                   struct Row4;
17022                   struct» Row5;
17023                   «struct Row6;
17024                   ˇ»
17025                   struct Row99;
17026                   struct Row9;
17027                   struct Row10;"#},
17028        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
17029        indoc! {r#"struct Row;
17030                   struct Row1;
17031                   struct Row33;
17032                   «ˇ
17033                   struct Row4;
17034                   struct» Row5;
17035                   «struct Row6;
17036                   ˇ»
17037                   struct Row99;
17038                   struct Row9;
17039                   struct Row10;"#},
17040        base_text,
17041        &mut cx,
17042    );
17043
17044    assert_hunk_revert(
17045        indoc! {r#"ˇstruct Row1.1;
17046                   struct Row1;
17047                   «ˇstr»uct Row22;
17048
17049                   struct ˇRow44;
17050                   struct Row5;
17051                   struct «Rˇ»ow66;ˇ
17052
17053                   «struˇ»ct Row88;
17054                   struct Row9;
17055                   struct Row1011;ˇ"#},
17056        vec![
17057            DiffHunkStatusKind::Modified,
17058            DiffHunkStatusKind::Modified,
17059            DiffHunkStatusKind::Modified,
17060            DiffHunkStatusKind::Modified,
17061            DiffHunkStatusKind::Modified,
17062            DiffHunkStatusKind::Modified,
17063        ],
17064        indoc! {r#"struct Row;
17065                   ˇstruct Row1;
17066                   struct Row2;
17067                   ˇ
17068                   struct Row4;
17069                   ˇstruct Row5;
17070                   struct Row6;
17071                   ˇ
17072                   struct Row8;
17073                   ˇstruct Row9;
17074                   struct Row10;ˇ"#},
17075        base_text,
17076        &mut cx,
17077    );
17078}
17079
17080#[gpui::test]
17081async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
17082    init_test(cx, |_| {});
17083    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
17084    let base_text = indoc! {r#"
17085        one
17086
17087        two
17088        three
17089        "#};
17090
17091    cx.set_head_text(base_text);
17092    cx.set_state("\nˇ\n");
17093    cx.executor().run_until_parked();
17094    cx.update_editor(|editor, _window, cx| {
17095        editor.expand_selected_diff_hunks(cx);
17096    });
17097    cx.executor().run_until_parked();
17098    cx.update_editor(|editor, window, cx| {
17099        editor.backspace(&Default::default(), window, cx);
17100    });
17101    cx.run_until_parked();
17102    cx.assert_state_with_diff(
17103        indoc! {r#"
17104
17105        - two
17106        - threeˇ
17107        +
17108        "#}
17109        .to_string(),
17110    );
17111}
17112
17113#[gpui::test]
17114async fn test_deletion_reverts(cx: &mut TestAppContext) {
17115    init_test(cx, |_| {});
17116    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
17117    let base_text = indoc! {r#"struct Row;
17118struct Row1;
17119struct Row2;
17120
17121struct Row4;
17122struct Row5;
17123struct Row6;
17124
17125struct Row8;
17126struct Row9;
17127struct Row10;"#};
17128
17129    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
17130    assert_hunk_revert(
17131        indoc! {r#"struct Row;
17132                   struct Row2;
17133
17134                   ˇstruct Row4;
17135                   struct Row5;
17136                   struct Row6;
17137                   ˇ
17138                   struct Row8;
17139                   struct Row10;"#},
17140        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
17141        indoc! {r#"struct Row;
17142                   struct Row2;
17143
17144                   ˇstruct Row4;
17145                   struct Row5;
17146                   struct Row6;
17147                   ˇ
17148                   struct Row8;
17149                   struct Row10;"#},
17150        base_text,
17151        &mut cx,
17152    );
17153    assert_hunk_revert(
17154        indoc! {r#"struct Row;
17155                   struct Row2;
17156
17157                   «ˇstruct Row4;
17158                   struct» Row5;
17159                   «struct Row6;
17160                   ˇ»
17161                   struct Row8;
17162                   struct Row10;"#},
17163        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
17164        indoc! {r#"struct Row;
17165                   struct Row2;
17166
17167                   «ˇstruct Row4;
17168                   struct» Row5;
17169                   «struct Row6;
17170                   ˇ»
17171                   struct Row8;
17172                   struct Row10;"#},
17173        base_text,
17174        &mut cx,
17175    );
17176
17177    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
17178    assert_hunk_revert(
17179        indoc! {r#"struct Row;
17180                   ˇstruct Row2;
17181
17182                   struct Row4;
17183                   struct Row5;
17184                   struct Row6;
17185
17186                   struct Row8;ˇ
17187                   struct Row10;"#},
17188        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
17189        indoc! {r#"struct Row;
17190                   struct Row1;
17191                   ˇstruct Row2;
17192
17193                   struct Row4;
17194                   struct Row5;
17195                   struct Row6;
17196
17197                   struct Row8;ˇ
17198                   struct Row9;
17199                   struct Row10;"#},
17200        base_text,
17201        &mut cx,
17202    );
17203    assert_hunk_revert(
17204        indoc! {r#"struct Row;
17205                   struct Row2«ˇ;
17206                   struct Row4;
17207                   struct» Row5;
17208                   «struct Row6;
17209
17210                   struct Row8;ˇ»
17211                   struct Row10;"#},
17212        vec![
17213            DiffHunkStatusKind::Deleted,
17214            DiffHunkStatusKind::Deleted,
17215            DiffHunkStatusKind::Deleted,
17216        ],
17217        indoc! {r#"struct Row;
17218                   struct Row1;
17219                   struct Row2«ˇ;
17220
17221                   struct Row4;
17222                   struct» Row5;
17223                   «struct Row6;
17224
17225                   struct Row8;ˇ»
17226                   struct Row9;
17227                   struct Row10;"#},
17228        base_text,
17229        &mut cx,
17230    );
17231}
17232
17233#[gpui::test]
17234async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
17235    init_test(cx, |_| {});
17236
17237    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
17238    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
17239    let base_text_3 =
17240        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
17241
17242    let text_1 = edit_first_char_of_every_line(base_text_1);
17243    let text_2 = edit_first_char_of_every_line(base_text_2);
17244    let text_3 = edit_first_char_of_every_line(base_text_3);
17245
17246    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
17247    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
17248    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
17249
17250    let multibuffer = cx.new(|cx| {
17251        let mut multibuffer = MultiBuffer::new(ReadWrite);
17252        multibuffer.push_excerpts(
17253            buffer_1.clone(),
17254            [
17255                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17256                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17257                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17258            ],
17259            cx,
17260        );
17261        multibuffer.push_excerpts(
17262            buffer_2.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_3.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
17280    });
17281
17282    let fs = FakeFs::new(cx.executor());
17283    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
17284    let (editor, cx) = cx
17285        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
17286    editor.update_in(cx, |editor, _window, cx| {
17287        for (buffer, diff_base) in [
17288            (buffer_1.clone(), base_text_1),
17289            (buffer_2.clone(), base_text_2),
17290            (buffer_3.clone(), base_text_3),
17291        ] {
17292            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
17293            editor
17294                .buffer
17295                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
17296        }
17297    });
17298    cx.executor().run_until_parked();
17299
17300    editor.update_in(cx, |editor, window, cx| {
17301        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}");
17302        editor.select_all(&SelectAll, window, cx);
17303        editor.git_restore(&Default::default(), window, cx);
17304    });
17305    cx.executor().run_until_parked();
17306
17307    // When all ranges are selected, all buffer hunks are reverted.
17308    editor.update(cx, |editor, cx| {
17309        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");
17310    });
17311    buffer_1.update(cx, |buffer, _| {
17312        assert_eq!(buffer.text(), base_text_1);
17313    });
17314    buffer_2.update(cx, |buffer, _| {
17315        assert_eq!(buffer.text(), base_text_2);
17316    });
17317    buffer_3.update(cx, |buffer, _| {
17318        assert_eq!(buffer.text(), base_text_3);
17319    });
17320
17321    editor.update_in(cx, |editor, window, cx| {
17322        editor.undo(&Default::default(), window, cx);
17323    });
17324
17325    editor.update_in(cx, |editor, window, cx| {
17326        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17327            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
17328        });
17329        editor.git_restore(&Default::default(), window, cx);
17330    });
17331
17332    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
17333    // but not affect buffer_2 and its related excerpts.
17334    editor.update(cx, |editor, cx| {
17335        assert_eq!(
17336            editor.text(cx),
17337            "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}"
17338        );
17339    });
17340    buffer_1.update(cx, |buffer, _| {
17341        assert_eq!(buffer.text(), base_text_1);
17342    });
17343    buffer_2.update(cx, |buffer, _| {
17344        assert_eq!(
17345            buffer.text(),
17346            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
17347        );
17348    });
17349    buffer_3.update(cx, |buffer, _| {
17350        assert_eq!(
17351            buffer.text(),
17352            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
17353        );
17354    });
17355
17356    fn edit_first_char_of_every_line(text: &str) -> String {
17357        text.split('\n')
17358            .map(|line| format!("X{}", &line[1..]))
17359            .collect::<Vec<_>>()
17360            .join("\n")
17361    }
17362}
17363
17364#[gpui::test]
17365async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
17366    init_test(cx, |_| {});
17367
17368    let cols = 4;
17369    let rows = 10;
17370    let sample_text_1 = sample_text(rows, cols, 'a');
17371    assert_eq!(
17372        sample_text_1,
17373        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
17374    );
17375    let sample_text_2 = sample_text(rows, cols, 'l');
17376    assert_eq!(
17377        sample_text_2,
17378        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
17379    );
17380    let sample_text_3 = sample_text(rows, cols, 'v');
17381    assert_eq!(
17382        sample_text_3,
17383        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
17384    );
17385
17386    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
17387    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
17388    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
17389
17390    let multi_buffer = cx.new(|cx| {
17391        let mut multibuffer = MultiBuffer::new(ReadWrite);
17392        multibuffer.push_excerpts(
17393            buffer_1.clone(),
17394            [
17395                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17396                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17397                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17398            ],
17399            cx,
17400        );
17401        multibuffer.push_excerpts(
17402            buffer_2.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_3.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
17420    });
17421
17422    let fs = FakeFs::new(cx.executor());
17423    fs.insert_tree(
17424        "/a",
17425        json!({
17426            "main.rs": sample_text_1,
17427            "other.rs": sample_text_2,
17428            "lib.rs": sample_text_3,
17429        }),
17430    )
17431    .await;
17432    let project = Project::test(fs, ["/a".as_ref()], cx).await;
17433    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17434    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17435    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
17436        Editor::new(
17437            EditorMode::full(),
17438            multi_buffer,
17439            Some(project.clone()),
17440            window,
17441            cx,
17442        )
17443    });
17444    let multibuffer_item_id = workspace
17445        .update(cx, |workspace, window, cx| {
17446            assert!(
17447                workspace.active_item(cx).is_none(),
17448                "active item should be None before the first item is added"
17449            );
17450            workspace.add_item_to_active_pane(
17451                Box::new(multi_buffer_editor.clone()),
17452                None,
17453                true,
17454                window,
17455                cx,
17456            );
17457            let active_item = workspace
17458                .active_item(cx)
17459                .expect("should have an active item after adding the multi buffer");
17460            assert!(
17461                !active_item.is_singleton(cx),
17462                "A multi buffer was expected to active after adding"
17463            );
17464            active_item.item_id()
17465        })
17466        .unwrap();
17467    cx.executor().run_until_parked();
17468
17469    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17470        editor.change_selections(
17471            SelectionEffects::scroll(Autoscroll::Next),
17472            window,
17473            cx,
17474            |s| s.select_ranges(Some(1..2)),
17475        );
17476        editor.open_excerpts(&OpenExcerpts, window, cx);
17477    });
17478    cx.executor().run_until_parked();
17479    let first_item_id = workspace
17480        .update(cx, |workspace, window, cx| {
17481            let active_item = workspace
17482                .active_item(cx)
17483                .expect("should have an active item after navigating into the 1st buffer");
17484            let first_item_id = active_item.item_id();
17485            assert_ne!(
17486                first_item_id, multibuffer_item_id,
17487                "Should navigate into the 1st buffer and activate it"
17488            );
17489            assert!(
17490                active_item.is_singleton(cx),
17491                "New active item should be a singleton buffer"
17492            );
17493            assert_eq!(
17494                active_item
17495                    .act_as::<Editor>(cx)
17496                    .expect("should have navigated into an editor for the 1st buffer")
17497                    .read(cx)
17498                    .text(cx),
17499                sample_text_1
17500            );
17501
17502            workspace
17503                .go_back(workspace.active_pane().downgrade(), window, cx)
17504                .detach_and_log_err(cx);
17505
17506            first_item_id
17507        })
17508        .unwrap();
17509    cx.executor().run_until_parked();
17510    workspace
17511        .update(cx, |workspace, _, cx| {
17512            let active_item = workspace
17513                .active_item(cx)
17514                .expect("should have an active item after navigating back");
17515            assert_eq!(
17516                active_item.item_id(),
17517                multibuffer_item_id,
17518                "Should navigate back to the multi buffer"
17519            );
17520            assert!(!active_item.is_singleton(cx));
17521        })
17522        .unwrap();
17523
17524    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17525        editor.change_selections(
17526            SelectionEffects::scroll(Autoscroll::Next),
17527            window,
17528            cx,
17529            |s| s.select_ranges(Some(39..40)),
17530        );
17531        editor.open_excerpts(&OpenExcerpts, window, cx);
17532    });
17533    cx.executor().run_until_parked();
17534    let second_item_id = workspace
17535        .update(cx, |workspace, window, cx| {
17536            let active_item = workspace
17537                .active_item(cx)
17538                .expect("should have an active item after navigating into the 2nd buffer");
17539            let second_item_id = active_item.item_id();
17540            assert_ne!(
17541                second_item_id, multibuffer_item_id,
17542                "Should navigate away from the multibuffer"
17543            );
17544            assert_ne!(
17545                second_item_id, first_item_id,
17546                "Should navigate into the 2nd buffer and activate it"
17547            );
17548            assert!(
17549                active_item.is_singleton(cx),
17550                "New active item should be a singleton buffer"
17551            );
17552            assert_eq!(
17553                active_item
17554                    .act_as::<Editor>(cx)
17555                    .expect("should have navigated into an editor")
17556                    .read(cx)
17557                    .text(cx),
17558                sample_text_2
17559            );
17560
17561            workspace
17562                .go_back(workspace.active_pane().downgrade(), window, cx)
17563                .detach_and_log_err(cx);
17564
17565            second_item_id
17566        })
17567        .unwrap();
17568    cx.executor().run_until_parked();
17569    workspace
17570        .update(cx, |workspace, _, cx| {
17571            let active_item = workspace
17572                .active_item(cx)
17573                .expect("should have an active item after navigating back from the 2nd buffer");
17574            assert_eq!(
17575                active_item.item_id(),
17576                multibuffer_item_id,
17577                "Should navigate back from the 2nd buffer to the multi buffer"
17578            );
17579            assert!(!active_item.is_singleton(cx));
17580        })
17581        .unwrap();
17582
17583    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17584        editor.change_selections(
17585            SelectionEffects::scroll(Autoscroll::Next),
17586            window,
17587            cx,
17588            |s| s.select_ranges(Some(70..70)),
17589        );
17590        editor.open_excerpts(&OpenExcerpts, window, cx);
17591    });
17592    cx.executor().run_until_parked();
17593    workspace
17594        .update(cx, |workspace, window, cx| {
17595            let active_item = workspace
17596                .active_item(cx)
17597                .expect("should have an active item after navigating into the 3rd buffer");
17598            let third_item_id = active_item.item_id();
17599            assert_ne!(
17600                third_item_id, multibuffer_item_id,
17601                "Should navigate into the 3rd buffer and activate it"
17602            );
17603            assert_ne!(third_item_id, first_item_id);
17604            assert_ne!(third_item_id, second_item_id);
17605            assert!(
17606                active_item.is_singleton(cx),
17607                "New active item should be a singleton buffer"
17608            );
17609            assert_eq!(
17610                active_item
17611                    .act_as::<Editor>(cx)
17612                    .expect("should have navigated into an editor")
17613                    .read(cx)
17614                    .text(cx),
17615                sample_text_3
17616            );
17617
17618            workspace
17619                .go_back(workspace.active_pane().downgrade(), window, cx)
17620                .detach_and_log_err(cx);
17621        })
17622        .unwrap();
17623    cx.executor().run_until_parked();
17624    workspace
17625        .update(cx, |workspace, _, cx| {
17626            let active_item = workspace
17627                .active_item(cx)
17628                .expect("should have an active item after navigating back from the 3rd buffer");
17629            assert_eq!(
17630                active_item.item_id(),
17631                multibuffer_item_id,
17632                "Should navigate back from the 3rd buffer to the multi buffer"
17633            );
17634            assert!(!active_item.is_singleton(cx));
17635        })
17636        .unwrap();
17637}
17638
17639#[gpui::test]
17640async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17641    init_test(cx, |_| {});
17642
17643    let mut cx = EditorTestContext::new(cx).await;
17644
17645    let diff_base = r#"
17646        use some::mod;
17647
17648        const A: u32 = 42;
17649
17650        fn main() {
17651            println!("hello");
17652
17653            println!("world");
17654        }
17655        "#
17656    .unindent();
17657
17658    cx.set_state(
17659        &r#"
17660        use some::modified;
17661
17662        ˇ
17663        fn main() {
17664            println!("hello there");
17665
17666            println!("around the");
17667            println!("world");
17668        }
17669        "#
17670        .unindent(),
17671    );
17672
17673    cx.set_head_text(&diff_base);
17674    executor.run_until_parked();
17675
17676    cx.update_editor(|editor, window, cx| {
17677        editor.go_to_next_hunk(&GoToHunk, window, cx);
17678        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17679    });
17680    executor.run_until_parked();
17681    cx.assert_state_with_diff(
17682        r#"
17683          use some::modified;
17684
17685
17686          fn main() {
17687        -     println!("hello");
17688        + ˇ    println!("hello there");
17689
17690              println!("around the");
17691              println!("world");
17692          }
17693        "#
17694        .unindent(),
17695    );
17696
17697    cx.update_editor(|editor, window, cx| {
17698        for _ in 0..2 {
17699            editor.go_to_next_hunk(&GoToHunk, window, cx);
17700            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17701        }
17702    });
17703    executor.run_until_parked();
17704    cx.assert_state_with_diff(
17705        r#"
17706        - use some::mod;
17707        + ˇuse some::modified;
17708
17709
17710          fn main() {
17711        -     println!("hello");
17712        +     println!("hello there");
17713
17714        +     println!("around the");
17715              println!("world");
17716          }
17717        "#
17718        .unindent(),
17719    );
17720
17721    cx.update_editor(|editor, window, cx| {
17722        editor.go_to_next_hunk(&GoToHunk, window, cx);
17723        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17724    });
17725    executor.run_until_parked();
17726    cx.assert_state_with_diff(
17727        r#"
17728        - use some::mod;
17729        + use some::modified;
17730
17731        - const A: u32 = 42;
17732          ˇ
17733          fn main() {
17734        -     println!("hello");
17735        +     println!("hello there");
17736
17737        +     println!("around the");
17738              println!("world");
17739          }
17740        "#
17741        .unindent(),
17742    );
17743
17744    cx.update_editor(|editor, window, cx| {
17745        editor.cancel(&Cancel, window, cx);
17746    });
17747
17748    cx.assert_state_with_diff(
17749        r#"
17750          use some::modified;
17751
17752          ˇ
17753          fn main() {
17754              println!("hello there");
17755
17756              println!("around the");
17757              println!("world");
17758          }
17759        "#
17760        .unindent(),
17761    );
17762}
17763
17764#[gpui::test]
17765async fn test_diff_base_change_with_expanded_diff_hunks(
17766    executor: BackgroundExecutor,
17767    cx: &mut TestAppContext,
17768) {
17769    init_test(cx, |_| {});
17770
17771    let mut cx = EditorTestContext::new(cx).await;
17772
17773    let diff_base = r#"
17774        use some::mod1;
17775        use some::mod2;
17776
17777        const A: u32 = 42;
17778        const B: u32 = 42;
17779        const C: u32 = 42;
17780
17781        fn main() {
17782            println!("hello");
17783
17784            println!("world");
17785        }
17786        "#
17787    .unindent();
17788
17789    cx.set_state(
17790        &r#"
17791        use some::mod2;
17792
17793        const A: u32 = 42;
17794        const C: u32 = 42;
17795
17796        fn main(ˇ) {
17797            //println!("hello");
17798
17799            println!("world");
17800            //
17801            //
17802        }
17803        "#
17804        .unindent(),
17805    );
17806
17807    cx.set_head_text(&diff_base);
17808    executor.run_until_parked();
17809
17810    cx.update_editor(|editor, window, cx| {
17811        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17812    });
17813    executor.run_until_parked();
17814    cx.assert_state_with_diff(
17815        r#"
17816        - use some::mod1;
17817          use some::mod2;
17818
17819          const A: u32 = 42;
17820        - const B: u32 = 42;
17821          const C: u32 = 42;
17822
17823          fn main(ˇ) {
17824        -     println!("hello");
17825        +     //println!("hello");
17826
17827              println!("world");
17828        +     //
17829        +     //
17830          }
17831        "#
17832        .unindent(),
17833    );
17834
17835    cx.set_head_text("new diff base!");
17836    executor.run_until_parked();
17837    cx.assert_state_with_diff(
17838        r#"
17839        - new diff base!
17840        + use some::mod2;
17841        +
17842        + const A: u32 = 42;
17843        + const C: u32 = 42;
17844        +
17845        + fn main(ˇ) {
17846        +     //println!("hello");
17847        +
17848        +     println!("world");
17849        +     //
17850        +     //
17851        + }
17852        "#
17853        .unindent(),
17854    );
17855}
17856
17857#[gpui::test]
17858async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
17859    init_test(cx, |_| {});
17860
17861    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
17862    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
17863    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
17864    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
17865    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
17866    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
17867
17868    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
17869    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
17870    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
17871
17872    let multi_buffer = cx.new(|cx| {
17873        let mut multibuffer = MultiBuffer::new(ReadWrite);
17874        multibuffer.push_excerpts(
17875            buffer_1.clone(),
17876            [
17877                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17878                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17879                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17880            ],
17881            cx,
17882        );
17883        multibuffer.push_excerpts(
17884            buffer_2.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_3.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
17902    });
17903
17904    let editor =
17905        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
17906    editor
17907        .update(cx, |editor, _window, cx| {
17908            for (buffer, diff_base) in [
17909                (buffer_1.clone(), file_1_old),
17910                (buffer_2.clone(), file_2_old),
17911                (buffer_3.clone(), file_3_old),
17912            ] {
17913                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
17914                editor
17915                    .buffer
17916                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
17917            }
17918        })
17919        .unwrap();
17920
17921    let mut cx = EditorTestContext::for_editor(editor, cx).await;
17922    cx.run_until_parked();
17923
17924    cx.assert_editor_state(
17925        &"
17926            ˇaaa
17927            ccc
17928            ddd
17929
17930            ggg
17931            hhh
17932
17933
17934            lll
17935            mmm
17936            NNN
17937
17938            qqq
17939            rrr
17940
17941            uuu
17942            111
17943            222
17944            333
17945
17946            666
17947            777
17948
17949            000
17950            !!!"
17951        .unindent(),
17952    );
17953
17954    cx.update_editor(|editor, window, cx| {
17955        editor.select_all(&SelectAll, window, cx);
17956        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17957    });
17958    cx.executor().run_until_parked();
17959
17960    cx.assert_state_with_diff(
17961        "
17962            «aaa
17963          - bbb
17964            ccc
17965            ddd
17966
17967            ggg
17968            hhh
17969
17970
17971            lll
17972            mmm
17973          - nnn
17974          + NNN
17975
17976            qqq
17977            rrr
17978
17979            uuu
17980            111
17981            222
17982            333
17983
17984          + 666
17985            777
17986
17987            000
17988            !!!ˇ»"
17989            .unindent(),
17990    );
17991}
17992
17993#[gpui::test]
17994async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
17995    init_test(cx, |_| {});
17996
17997    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
17998    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
17999
18000    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
18001    let multi_buffer = cx.new(|cx| {
18002        let mut multibuffer = MultiBuffer::new(ReadWrite);
18003        multibuffer.push_excerpts(
18004            buffer.clone(),
18005            [
18006                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
18007                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
18008                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
18009            ],
18010            cx,
18011        );
18012        multibuffer
18013    });
18014
18015    let editor =
18016        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
18017    editor
18018        .update(cx, |editor, _window, cx| {
18019            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
18020            editor
18021                .buffer
18022                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
18023        })
18024        .unwrap();
18025
18026    let mut cx = EditorTestContext::for_editor(editor, cx).await;
18027    cx.run_until_parked();
18028
18029    cx.update_editor(|editor, window, cx| {
18030        editor.expand_all_diff_hunks(&Default::default(), window, cx)
18031    });
18032    cx.executor().run_until_parked();
18033
18034    // When the start of a hunk coincides with the start of its excerpt,
18035    // the hunk is expanded. When the start of a a hunk is earlier than
18036    // the start of its excerpt, the hunk is not expanded.
18037    cx.assert_state_with_diff(
18038        "
18039            ˇaaa
18040          - bbb
18041          + BBB
18042
18043          - ddd
18044          - eee
18045          + DDD
18046          + EEE
18047            fff
18048
18049            iii
18050        "
18051        .unindent(),
18052    );
18053}
18054
18055#[gpui::test]
18056async fn test_edits_around_expanded_insertion_hunks(
18057    executor: BackgroundExecutor,
18058    cx: &mut TestAppContext,
18059) {
18060    init_test(cx, |_| {});
18061
18062    let mut cx = EditorTestContext::new(cx).await;
18063
18064    let diff_base = r#"
18065        use some::mod1;
18066        use some::mod2;
18067
18068        const A: u32 = 42;
18069
18070        fn main() {
18071            println!("hello");
18072
18073            println!("world");
18074        }
18075        "#
18076    .unindent();
18077    executor.run_until_parked();
18078    cx.set_state(
18079        &r#"
18080        use some::mod1;
18081        use some::mod2;
18082
18083        const A: u32 = 42;
18084        const B: u32 = 42;
18085        const C: u32 = 42;
18086        ˇ
18087
18088        fn main() {
18089            println!("hello");
18090
18091            println!("world");
18092        }
18093        "#
18094        .unindent(),
18095    );
18096
18097    cx.set_head_text(&diff_base);
18098    executor.run_until_parked();
18099
18100    cx.update_editor(|editor, window, cx| {
18101        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18102    });
18103    executor.run_until_parked();
18104
18105    cx.assert_state_with_diff(
18106        r#"
18107        use some::mod1;
18108        use some::mod2;
18109
18110        const A: u32 = 42;
18111      + const B: u32 = 42;
18112      + const C: u32 = 42;
18113      + ˇ
18114
18115        fn main() {
18116            println!("hello");
18117
18118            println!("world");
18119        }
18120      "#
18121        .unindent(),
18122    );
18123
18124    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
18125    executor.run_until_parked();
18126
18127    cx.assert_state_with_diff(
18128        r#"
18129        use some::mod1;
18130        use some::mod2;
18131
18132        const A: u32 = 42;
18133      + const B: u32 = 42;
18134      + const C: u32 = 42;
18135      + const D: u32 = 42;
18136      + ˇ
18137
18138        fn main() {
18139            println!("hello");
18140
18141            println!("world");
18142        }
18143      "#
18144        .unindent(),
18145    );
18146
18147    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
18148    executor.run_until_parked();
18149
18150    cx.assert_state_with_diff(
18151        r#"
18152        use some::mod1;
18153        use some::mod2;
18154
18155        const A: u32 = 42;
18156      + const B: u32 = 42;
18157      + const C: u32 = 42;
18158      + const D: u32 = 42;
18159      + const E: u32 = 42;
18160      + ˇ
18161
18162        fn main() {
18163            println!("hello");
18164
18165            println!("world");
18166        }
18167      "#
18168        .unindent(),
18169    );
18170
18171    cx.update_editor(|editor, window, cx| {
18172        editor.delete_line(&DeleteLine, window, cx);
18173    });
18174    executor.run_until_parked();
18175
18176    cx.assert_state_with_diff(
18177        r#"
18178        use some::mod1;
18179        use some::mod2;
18180
18181        const A: u32 = 42;
18182      + const B: u32 = 42;
18183      + const C: u32 = 42;
18184      + const D: u32 = 42;
18185      + const E: u32 = 42;
18186        ˇ
18187        fn main() {
18188            println!("hello");
18189
18190            println!("world");
18191        }
18192      "#
18193        .unindent(),
18194    );
18195
18196    cx.update_editor(|editor, window, cx| {
18197        editor.move_up(&MoveUp, window, cx);
18198        editor.delete_line(&DeleteLine, window, cx);
18199        editor.move_up(&MoveUp, window, cx);
18200        editor.delete_line(&DeleteLine, window, cx);
18201        editor.move_up(&MoveUp, window, cx);
18202        editor.delete_line(&DeleteLine, window, cx);
18203    });
18204    executor.run_until_parked();
18205    cx.assert_state_with_diff(
18206        r#"
18207        use some::mod1;
18208        use some::mod2;
18209
18210        const A: u32 = 42;
18211      + const B: u32 = 42;
18212        ˇ
18213        fn main() {
18214            println!("hello");
18215
18216            println!("world");
18217        }
18218      "#
18219        .unindent(),
18220    );
18221
18222    cx.update_editor(|editor, window, cx| {
18223        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
18224        editor.delete_line(&DeleteLine, window, cx);
18225    });
18226    executor.run_until_parked();
18227    cx.assert_state_with_diff(
18228        r#"
18229        ˇ
18230        fn main() {
18231            println!("hello");
18232
18233            println!("world");
18234        }
18235      "#
18236        .unindent(),
18237    );
18238}
18239
18240#[gpui::test]
18241async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
18242    init_test(cx, |_| {});
18243
18244    let mut cx = EditorTestContext::new(cx).await;
18245    cx.set_head_text(indoc! { "
18246        one
18247        two
18248        three
18249        four
18250        five
18251        "
18252    });
18253    cx.set_state(indoc! { "
18254        one
18255        ˇthree
18256        five
18257    "});
18258    cx.run_until_parked();
18259    cx.update_editor(|editor, window, cx| {
18260        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
18261    });
18262    cx.assert_state_with_diff(
18263        indoc! { "
18264        one
18265      - two
18266        ˇthree
18267      - four
18268        five
18269    "}
18270        .to_string(),
18271    );
18272    cx.update_editor(|editor, window, cx| {
18273        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
18274    });
18275
18276    cx.assert_state_with_diff(
18277        indoc! { "
18278        one
18279        ˇthree
18280        five
18281    "}
18282        .to_string(),
18283    );
18284
18285    cx.set_state(indoc! { "
18286        one
18287        ˇTWO
18288        three
18289        four
18290        five
18291    "});
18292    cx.run_until_parked();
18293    cx.update_editor(|editor, window, cx| {
18294        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
18295    });
18296
18297    cx.assert_state_with_diff(
18298        indoc! { "
18299            one
18300          - two
18301          + ˇTWO
18302            three
18303            four
18304            five
18305        "}
18306        .to_string(),
18307    );
18308    cx.update_editor(|editor, window, cx| {
18309        editor.move_up(&Default::default(), window, cx);
18310        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
18311    });
18312    cx.assert_state_with_diff(
18313        indoc! { "
18314            one
18315            ˇTWO
18316            three
18317            four
18318            five
18319        "}
18320        .to_string(),
18321    );
18322}
18323
18324#[gpui::test]
18325async fn test_edits_around_expanded_deletion_hunks(
18326    executor: BackgroundExecutor,
18327    cx: &mut TestAppContext,
18328) {
18329    init_test(cx, |_| {});
18330
18331    let mut cx = EditorTestContext::new(cx).await;
18332
18333    let diff_base = r#"
18334        use some::mod1;
18335        use some::mod2;
18336
18337        const A: u32 = 42;
18338        const B: u32 = 42;
18339        const C: u32 = 42;
18340
18341
18342        fn main() {
18343            println!("hello");
18344
18345            println!("world");
18346        }
18347    "#
18348    .unindent();
18349    executor.run_until_parked();
18350    cx.set_state(
18351        &r#"
18352        use some::mod1;
18353        use some::mod2;
18354
18355        ˇconst B: u32 = 42;
18356        const C: u32 = 42;
18357
18358
18359        fn main() {
18360            println!("hello");
18361
18362            println!("world");
18363        }
18364        "#
18365        .unindent(),
18366    );
18367
18368    cx.set_head_text(&diff_base);
18369    executor.run_until_parked();
18370
18371    cx.update_editor(|editor, window, cx| {
18372        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18373    });
18374    executor.run_until_parked();
18375
18376    cx.assert_state_with_diff(
18377        r#"
18378        use some::mod1;
18379        use some::mod2;
18380
18381      - const A: u32 = 42;
18382        ˇconst B: u32 = 42;
18383        const C: u32 = 42;
18384
18385
18386        fn main() {
18387            println!("hello");
18388
18389            println!("world");
18390        }
18391      "#
18392        .unindent(),
18393    );
18394
18395    cx.update_editor(|editor, window, cx| {
18396        editor.delete_line(&DeleteLine, window, cx);
18397    });
18398    executor.run_until_parked();
18399    cx.assert_state_with_diff(
18400        r#"
18401        use some::mod1;
18402        use some::mod2;
18403
18404      - const A: u32 = 42;
18405      - const B: u32 = 42;
18406        ˇconst C: u32 = 42;
18407
18408
18409        fn main() {
18410            println!("hello");
18411
18412            println!("world");
18413        }
18414      "#
18415        .unindent(),
18416    );
18417
18418    cx.update_editor(|editor, window, cx| {
18419        editor.delete_line(&DeleteLine, window, cx);
18420    });
18421    executor.run_until_parked();
18422    cx.assert_state_with_diff(
18423        r#"
18424        use some::mod1;
18425        use some::mod2;
18426
18427      - const A: u32 = 42;
18428      - const B: u32 = 42;
18429      - const C: u32 = 42;
18430        ˇ
18431
18432        fn main() {
18433            println!("hello");
18434
18435            println!("world");
18436        }
18437      "#
18438        .unindent(),
18439    );
18440
18441    cx.update_editor(|editor, window, cx| {
18442        editor.handle_input("replacement", window, cx);
18443    });
18444    executor.run_until_parked();
18445    cx.assert_state_with_diff(
18446        r#"
18447        use some::mod1;
18448        use some::mod2;
18449
18450      - const A: u32 = 42;
18451      - const B: u32 = 42;
18452      - const C: u32 = 42;
18453      -
18454      + replacementˇ
18455
18456        fn main() {
18457            println!("hello");
18458
18459            println!("world");
18460        }
18461      "#
18462        .unindent(),
18463    );
18464}
18465
18466#[gpui::test]
18467async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18468    init_test(cx, |_| {});
18469
18470    let mut cx = EditorTestContext::new(cx).await;
18471
18472    let base_text = r#"
18473        one
18474        two
18475        three
18476        four
18477        five
18478    "#
18479    .unindent();
18480    executor.run_until_parked();
18481    cx.set_state(
18482        &r#"
18483        one
18484        two
18485        fˇour
18486        five
18487        "#
18488        .unindent(),
18489    );
18490
18491    cx.set_head_text(&base_text);
18492    executor.run_until_parked();
18493
18494    cx.update_editor(|editor, window, cx| {
18495        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18496    });
18497    executor.run_until_parked();
18498
18499    cx.assert_state_with_diff(
18500        r#"
18501          one
18502          two
18503        - three
18504          fˇour
18505          five
18506        "#
18507        .unindent(),
18508    );
18509
18510    cx.update_editor(|editor, window, cx| {
18511        editor.backspace(&Backspace, window, cx);
18512        editor.backspace(&Backspace, window, cx);
18513    });
18514    executor.run_until_parked();
18515    cx.assert_state_with_diff(
18516        r#"
18517          one
18518          two
18519        - threeˇ
18520        - four
18521        + our
18522          five
18523        "#
18524        .unindent(),
18525    );
18526}
18527
18528#[gpui::test]
18529async fn test_edit_after_expanded_modification_hunk(
18530    executor: BackgroundExecutor,
18531    cx: &mut TestAppContext,
18532) {
18533    init_test(cx, |_| {});
18534
18535    let mut cx = EditorTestContext::new(cx).await;
18536
18537    let diff_base = r#"
18538        use some::mod1;
18539        use some::mod2;
18540
18541        const A: u32 = 42;
18542        const B: u32 = 42;
18543        const C: u32 = 42;
18544        const D: u32 = 42;
18545
18546
18547        fn main() {
18548            println!("hello");
18549
18550            println!("world");
18551        }"#
18552    .unindent();
18553
18554    cx.set_state(
18555        &r#"
18556        use some::mod1;
18557        use some::mod2;
18558
18559        const A: u32 = 42;
18560        const B: u32 = 42;
18561        const C: u32 = 43ˇ
18562        const D: u32 = 42;
18563
18564
18565        fn main() {
18566            println!("hello");
18567
18568            println!("world");
18569        }"#
18570        .unindent(),
18571    );
18572
18573    cx.set_head_text(&diff_base);
18574    executor.run_until_parked();
18575    cx.update_editor(|editor, window, cx| {
18576        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18577    });
18578    executor.run_until_parked();
18579
18580    cx.assert_state_with_diff(
18581        r#"
18582        use some::mod1;
18583        use some::mod2;
18584
18585        const A: u32 = 42;
18586        const B: u32 = 42;
18587      - const C: u32 = 42;
18588      + const C: u32 = 43ˇ
18589        const D: u32 = 42;
18590
18591
18592        fn main() {
18593            println!("hello");
18594
18595            println!("world");
18596        }"#
18597        .unindent(),
18598    );
18599
18600    cx.update_editor(|editor, window, cx| {
18601        editor.handle_input("\nnew_line\n", window, cx);
18602    });
18603    executor.run_until_parked();
18604
18605    cx.assert_state_with_diff(
18606        r#"
18607        use some::mod1;
18608        use some::mod2;
18609
18610        const A: u32 = 42;
18611        const B: u32 = 42;
18612      - const C: u32 = 42;
18613      + const C: u32 = 43
18614      + new_line
18615      + ˇ
18616        const D: u32 = 42;
18617
18618
18619        fn main() {
18620            println!("hello");
18621
18622            println!("world");
18623        }"#
18624        .unindent(),
18625    );
18626}
18627
18628#[gpui::test]
18629async fn test_stage_and_unstage_added_file_hunk(
18630    executor: BackgroundExecutor,
18631    cx: &mut TestAppContext,
18632) {
18633    init_test(cx, |_| {});
18634
18635    let mut cx = EditorTestContext::new(cx).await;
18636    cx.update_editor(|editor, _, cx| {
18637        editor.set_expand_all_diff_hunks(cx);
18638    });
18639
18640    let working_copy = r#"
18641            ˇfn main() {
18642                println!("hello, world!");
18643            }
18644        "#
18645    .unindent();
18646
18647    cx.set_state(&working_copy);
18648    executor.run_until_parked();
18649
18650    cx.assert_state_with_diff(
18651        r#"
18652            + ˇfn main() {
18653            +     println!("hello, world!");
18654            + }
18655        "#
18656        .unindent(),
18657    );
18658    cx.assert_index_text(None);
18659
18660    cx.update_editor(|editor, window, cx| {
18661        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18662    });
18663    executor.run_until_parked();
18664    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
18665    cx.assert_state_with_diff(
18666        r#"
18667            + ˇfn main() {
18668            +     println!("hello, world!");
18669            + }
18670        "#
18671        .unindent(),
18672    );
18673
18674    cx.update_editor(|editor, window, cx| {
18675        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18676    });
18677    executor.run_until_parked();
18678    cx.assert_index_text(None);
18679}
18680
18681async fn setup_indent_guides_editor(
18682    text: &str,
18683    cx: &mut TestAppContext,
18684) -> (BufferId, EditorTestContext) {
18685    init_test(cx, |_| {});
18686
18687    let mut cx = EditorTestContext::new(cx).await;
18688
18689    let buffer_id = cx.update_editor(|editor, window, cx| {
18690        editor.set_text(text, window, cx);
18691        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
18692
18693        buffer_ids[0]
18694    });
18695
18696    (buffer_id, cx)
18697}
18698
18699fn assert_indent_guides(
18700    range: Range<u32>,
18701    expected: Vec<IndentGuide>,
18702    active_indices: Option<Vec<usize>>,
18703    cx: &mut EditorTestContext,
18704) {
18705    let indent_guides = cx.update_editor(|editor, window, cx| {
18706        let snapshot = editor.snapshot(window, cx).display_snapshot;
18707        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
18708            editor,
18709            MultiBufferRow(range.start)..MultiBufferRow(range.end),
18710            true,
18711            &snapshot,
18712            cx,
18713        );
18714
18715        indent_guides.sort_by(|a, b| {
18716            a.depth.cmp(&b.depth).then(
18717                a.start_row
18718                    .cmp(&b.start_row)
18719                    .then(a.end_row.cmp(&b.end_row)),
18720            )
18721        });
18722        indent_guides
18723    });
18724
18725    if let Some(expected) = active_indices {
18726        let active_indices = cx.update_editor(|editor, window, cx| {
18727            let snapshot = editor.snapshot(window, cx).display_snapshot;
18728            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
18729        });
18730
18731        assert_eq!(
18732            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
18733            expected,
18734            "Active indent guide indices do not match"
18735        );
18736    }
18737
18738    assert_eq!(indent_guides, expected, "Indent guides do not match");
18739}
18740
18741fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
18742    IndentGuide {
18743        buffer_id,
18744        start_row: MultiBufferRow(start_row),
18745        end_row: MultiBufferRow(end_row),
18746        depth,
18747        tab_size: 4,
18748        settings: IndentGuideSettings {
18749            enabled: true,
18750            line_width: 1,
18751            active_line_width: 1,
18752            ..Default::default()
18753        },
18754    }
18755}
18756
18757#[gpui::test]
18758async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
18759    let (buffer_id, mut cx) = setup_indent_guides_editor(
18760        &"
18761        fn main() {
18762            let a = 1;
18763        }"
18764        .unindent(),
18765        cx,
18766    )
18767    .await;
18768
18769    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
18770}
18771
18772#[gpui::test]
18773async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
18774    let (buffer_id, mut cx) = setup_indent_guides_editor(
18775        &"
18776        fn main() {
18777            let a = 1;
18778            let b = 2;
18779        }"
18780        .unindent(),
18781        cx,
18782    )
18783    .await;
18784
18785    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
18786}
18787
18788#[gpui::test]
18789async fn test_indent_guide_nested(cx: &mut TestAppContext) {
18790    let (buffer_id, mut cx) = setup_indent_guides_editor(
18791        &"
18792        fn main() {
18793            let a = 1;
18794            if a == 3 {
18795                let b = 2;
18796            } else {
18797                let c = 3;
18798            }
18799        }"
18800        .unindent(),
18801        cx,
18802    )
18803    .await;
18804
18805    assert_indent_guides(
18806        0..8,
18807        vec![
18808            indent_guide(buffer_id, 1, 6, 0),
18809            indent_guide(buffer_id, 3, 3, 1),
18810            indent_guide(buffer_id, 5, 5, 1),
18811        ],
18812        None,
18813        &mut cx,
18814    );
18815}
18816
18817#[gpui::test]
18818async fn test_indent_guide_tab(cx: &mut TestAppContext) {
18819    let (buffer_id, mut cx) = setup_indent_guides_editor(
18820        &"
18821        fn main() {
18822            let a = 1;
18823                let b = 2;
18824            let c = 3;
18825        }"
18826        .unindent(),
18827        cx,
18828    )
18829    .await;
18830
18831    assert_indent_guides(
18832        0..5,
18833        vec![
18834            indent_guide(buffer_id, 1, 3, 0),
18835            indent_guide(buffer_id, 2, 2, 1),
18836        ],
18837        None,
18838        &mut cx,
18839    );
18840}
18841
18842#[gpui::test]
18843async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
18844    let (buffer_id, mut cx) = setup_indent_guides_editor(
18845        &"
18846        fn main() {
18847            let a = 1;
18848
18849            let c = 3;
18850        }"
18851        .unindent(),
18852        cx,
18853    )
18854    .await;
18855
18856    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
18857}
18858
18859#[gpui::test]
18860async fn test_indent_guide_complex(cx: &mut TestAppContext) {
18861    let (buffer_id, mut cx) = setup_indent_guides_editor(
18862        &"
18863        fn main() {
18864            let a = 1;
18865
18866            let c = 3;
18867
18868            if a == 3 {
18869                let b = 2;
18870            } else {
18871                let c = 3;
18872            }
18873        }"
18874        .unindent(),
18875        cx,
18876    )
18877    .await;
18878
18879    assert_indent_guides(
18880        0..11,
18881        vec![
18882            indent_guide(buffer_id, 1, 9, 0),
18883            indent_guide(buffer_id, 6, 6, 1),
18884            indent_guide(buffer_id, 8, 8, 1),
18885        ],
18886        None,
18887        &mut cx,
18888    );
18889}
18890
18891#[gpui::test]
18892async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
18893    let (buffer_id, mut cx) = setup_indent_guides_editor(
18894        &"
18895        fn main() {
18896            let a = 1;
18897
18898            let c = 3;
18899
18900            if a == 3 {
18901                let b = 2;
18902            } else {
18903                let c = 3;
18904            }
18905        }"
18906        .unindent(),
18907        cx,
18908    )
18909    .await;
18910
18911    assert_indent_guides(
18912        1..11,
18913        vec![
18914            indent_guide(buffer_id, 1, 9, 0),
18915            indent_guide(buffer_id, 6, 6, 1),
18916            indent_guide(buffer_id, 8, 8, 1),
18917        ],
18918        None,
18919        &mut cx,
18920    );
18921}
18922
18923#[gpui::test]
18924async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
18925    let (buffer_id, mut cx) = setup_indent_guides_editor(
18926        &"
18927        fn main() {
18928            let a = 1;
18929
18930            let c = 3;
18931
18932            if a == 3 {
18933                let b = 2;
18934            } else {
18935                let c = 3;
18936            }
18937        }"
18938        .unindent(),
18939        cx,
18940    )
18941    .await;
18942
18943    assert_indent_guides(
18944        1..10,
18945        vec![
18946            indent_guide(buffer_id, 1, 9, 0),
18947            indent_guide(buffer_id, 6, 6, 1),
18948            indent_guide(buffer_id, 8, 8, 1),
18949        ],
18950        None,
18951        &mut cx,
18952    );
18953}
18954
18955#[gpui::test]
18956async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
18957    let (buffer_id, mut cx) = setup_indent_guides_editor(
18958        &"
18959        fn main() {
18960            if a {
18961                b(
18962                    c,
18963                    d,
18964                )
18965            } else {
18966                e(
18967                    f
18968                )
18969            }
18970        }"
18971        .unindent(),
18972        cx,
18973    )
18974    .await;
18975
18976    assert_indent_guides(
18977        0..11,
18978        vec![
18979            indent_guide(buffer_id, 1, 10, 0),
18980            indent_guide(buffer_id, 2, 5, 1),
18981            indent_guide(buffer_id, 7, 9, 1),
18982            indent_guide(buffer_id, 3, 4, 2),
18983            indent_guide(buffer_id, 8, 8, 2),
18984        ],
18985        None,
18986        &mut cx,
18987    );
18988
18989    cx.update_editor(|editor, window, cx| {
18990        editor.fold_at(MultiBufferRow(2), window, cx);
18991        assert_eq!(
18992            editor.display_text(cx),
18993            "
18994            fn main() {
18995                if a {
18996                    b(⋯
18997                    )
18998                } else {
18999                    e(
19000                        f
19001                    )
19002                }
19003            }"
19004            .unindent()
19005        );
19006    });
19007
19008    assert_indent_guides(
19009        0..11,
19010        vec![
19011            indent_guide(buffer_id, 1, 10, 0),
19012            indent_guide(buffer_id, 2, 5, 1),
19013            indent_guide(buffer_id, 7, 9, 1),
19014            indent_guide(buffer_id, 8, 8, 2),
19015        ],
19016        None,
19017        &mut cx,
19018    );
19019}
19020
19021#[gpui::test]
19022async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
19023    let (buffer_id, mut cx) = setup_indent_guides_editor(
19024        &"
19025        block1
19026            block2
19027                block3
19028                    block4
19029            block2
19030        block1
19031        block1"
19032            .unindent(),
19033        cx,
19034    )
19035    .await;
19036
19037    assert_indent_guides(
19038        1..10,
19039        vec![
19040            indent_guide(buffer_id, 1, 4, 0),
19041            indent_guide(buffer_id, 2, 3, 1),
19042            indent_guide(buffer_id, 3, 3, 2),
19043        ],
19044        None,
19045        &mut cx,
19046    );
19047}
19048
19049#[gpui::test]
19050async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
19051    let (buffer_id, mut cx) = setup_indent_guides_editor(
19052        &"
19053        block1
19054            block2
19055                block3
19056
19057        block1
19058        block1"
19059            .unindent(),
19060        cx,
19061    )
19062    .await;
19063
19064    assert_indent_guides(
19065        0..6,
19066        vec![
19067            indent_guide(buffer_id, 1, 2, 0),
19068            indent_guide(buffer_id, 2, 2, 1),
19069        ],
19070        None,
19071        &mut cx,
19072    );
19073}
19074
19075#[gpui::test]
19076async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
19077    let (buffer_id, mut cx) = setup_indent_guides_editor(
19078        &"
19079        function component() {
19080        \treturn (
19081        \t\t\t
19082        \t\t<div>
19083        \t\t\t<abc></abc>
19084        \t\t</div>
19085        \t)
19086        }"
19087        .unindent(),
19088        cx,
19089    )
19090    .await;
19091
19092    assert_indent_guides(
19093        0..8,
19094        vec![
19095            indent_guide(buffer_id, 1, 6, 0),
19096            indent_guide(buffer_id, 2, 5, 1),
19097            indent_guide(buffer_id, 4, 4, 2),
19098        ],
19099        None,
19100        &mut cx,
19101    );
19102}
19103
19104#[gpui::test]
19105async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
19106    let (buffer_id, mut cx) = setup_indent_guides_editor(
19107        &"
19108        function component() {
19109        \treturn (
19110        \t
19111        \t\t<div>
19112        \t\t\t<abc></abc>
19113        \t\t</div>
19114        \t)
19115        }"
19116        .unindent(),
19117        cx,
19118    )
19119    .await;
19120
19121    assert_indent_guides(
19122        0..8,
19123        vec![
19124            indent_guide(buffer_id, 1, 6, 0),
19125            indent_guide(buffer_id, 2, 5, 1),
19126            indent_guide(buffer_id, 4, 4, 2),
19127        ],
19128        None,
19129        &mut cx,
19130    );
19131}
19132
19133#[gpui::test]
19134async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
19135    let (buffer_id, mut cx) = setup_indent_guides_editor(
19136        &"
19137        block1
19138
19139
19140
19141            block2
19142        "
19143        .unindent(),
19144        cx,
19145    )
19146    .await;
19147
19148    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
19149}
19150
19151#[gpui::test]
19152async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
19153    let (buffer_id, mut cx) = setup_indent_guides_editor(
19154        &"
19155        def a:
19156        \tb = 3
19157        \tif True:
19158        \t\tc = 4
19159        \t\td = 5
19160        \tprint(b)
19161        "
19162        .unindent(),
19163        cx,
19164    )
19165    .await;
19166
19167    assert_indent_guides(
19168        0..6,
19169        vec![
19170            indent_guide(buffer_id, 1, 5, 0),
19171            indent_guide(buffer_id, 3, 4, 1),
19172        ],
19173        None,
19174        &mut cx,
19175    );
19176}
19177
19178#[gpui::test]
19179async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
19180    let (buffer_id, mut cx) = setup_indent_guides_editor(
19181        &"
19182    fn main() {
19183        let a = 1;
19184    }"
19185        .unindent(),
19186        cx,
19187    )
19188    .await;
19189
19190    cx.update_editor(|editor, window, cx| {
19191        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19192            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
19193        });
19194    });
19195
19196    assert_indent_guides(
19197        0..3,
19198        vec![indent_guide(buffer_id, 1, 1, 0)],
19199        Some(vec![0]),
19200        &mut cx,
19201    );
19202}
19203
19204#[gpui::test]
19205async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
19206    let (buffer_id, mut cx) = setup_indent_guides_editor(
19207        &"
19208    fn main() {
19209        if 1 == 2 {
19210            let a = 1;
19211        }
19212    }"
19213        .unindent(),
19214        cx,
19215    )
19216    .await;
19217
19218    cx.update_editor(|editor, window, cx| {
19219        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19220            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
19221        });
19222    });
19223
19224    assert_indent_guides(
19225        0..4,
19226        vec![
19227            indent_guide(buffer_id, 1, 3, 0),
19228            indent_guide(buffer_id, 2, 2, 1),
19229        ],
19230        Some(vec![1]),
19231        &mut cx,
19232    );
19233
19234    cx.update_editor(|editor, window, cx| {
19235        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19236            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
19237        });
19238    });
19239
19240    assert_indent_guides(
19241        0..4,
19242        vec![
19243            indent_guide(buffer_id, 1, 3, 0),
19244            indent_guide(buffer_id, 2, 2, 1),
19245        ],
19246        Some(vec![1]),
19247        &mut cx,
19248    );
19249
19250    cx.update_editor(|editor, window, cx| {
19251        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19252            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
19253        });
19254    });
19255
19256    assert_indent_guides(
19257        0..4,
19258        vec![
19259            indent_guide(buffer_id, 1, 3, 0),
19260            indent_guide(buffer_id, 2, 2, 1),
19261        ],
19262        Some(vec![0]),
19263        &mut cx,
19264    );
19265}
19266
19267#[gpui::test]
19268async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
19269    let (buffer_id, mut cx) = setup_indent_guides_editor(
19270        &"
19271    fn main() {
19272        let a = 1;
19273
19274        let b = 2;
19275    }"
19276        .unindent(),
19277        cx,
19278    )
19279    .await;
19280
19281    cx.update_editor(|editor, window, cx| {
19282        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19283            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
19284        });
19285    });
19286
19287    assert_indent_guides(
19288        0..5,
19289        vec![indent_guide(buffer_id, 1, 3, 0)],
19290        Some(vec![0]),
19291        &mut cx,
19292    );
19293}
19294
19295#[gpui::test]
19296async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
19297    let (buffer_id, mut cx) = setup_indent_guides_editor(
19298        &"
19299    def m:
19300        a = 1
19301        pass"
19302            .unindent(),
19303        cx,
19304    )
19305    .await;
19306
19307    cx.update_editor(|editor, window, cx| {
19308        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19309            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
19310        });
19311    });
19312
19313    assert_indent_guides(
19314        0..3,
19315        vec![indent_guide(buffer_id, 1, 2, 0)],
19316        Some(vec![0]),
19317        &mut cx,
19318    );
19319}
19320
19321#[gpui::test]
19322async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
19323    init_test(cx, |_| {});
19324    let mut cx = EditorTestContext::new(cx).await;
19325    let text = indoc! {
19326        "
19327        impl A {
19328            fn b() {
19329                0;
19330                3;
19331                5;
19332                6;
19333                7;
19334            }
19335        }
19336        "
19337    };
19338    let base_text = indoc! {
19339        "
19340        impl A {
19341            fn b() {
19342                0;
19343                1;
19344                2;
19345                3;
19346                4;
19347            }
19348            fn c() {
19349                5;
19350                6;
19351                7;
19352            }
19353        }
19354        "
19355    };
19356
19357    cx.update_editor(|editor, window, cx| {
19358        editor.set_text(text, window, cx);
19359
19360        editor.buffer().update(cx, |multibuffer, cx| {
19361            let buffer = multibuffer.as_singleton().unwrap();
19362            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
19363
19364            multibuffer.set_all_diff_hunks_expanded(cx);
19365            multibuffer.add_diff(diff, cx);
19366
19367            buffer.read(cx).remote_id()
19368        })
19369    });
19370    cx.run_until_parked();
19371
19372    cx.assert_state_with_diff(
19373        indoc! { "
19374          impl A {
19375              fn b() {
19376                  0;
19377        -         1;
19378        -         2;
19379                  3;
19380        -         4;
19381        -     }
19382        -     fn c() {
19383                  5;
19384                  6;
19385                  7;
19386              }
19387          }
19388          ˇ"
19389        }
19390        .to_string(),
19391    );
19392
19393    let mut actual_guides = cx.update_editor(|editor, window, cx| {
19394        editor
19395            .snapshot(window, cx)
19396            .buffer_snapshot
19397            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
19398            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
19399            .collect::<Vec<_>>()
19400    });
19401    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
19402    assert_eq!(
19403        actual_guides,
19404        vec![
19405            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
19406            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
19407            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
19408        ]
19409    );
19410}
19411
19412#[gpui::test]
19413async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19414    init_test(cx, |_| {});
19415    let mut cx = EditorTestContext::new(cx).await;
19416
19417    let diff_base = r#"
19418        a
19419        b
19420        c
19421        "#
19422    .unindent();
19423
19424    cx.set_state(
19425        &r#"
19426        ˇA
19427        b
19428        C
19429        "#
19430        .unindent(),
19431    );
19432    cx.set_head_text(&diff_base);
19433    cx.update_editor(|editor, window, cx| {
19434        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19435    });
19436    executor.run_until_parked();
19437
19438    let both_hunks_expanded = r#"
19439        - a
19440        + ˇA
19441          b
19442        - c
19443        + C
19444        "#
19445    .unindent();
19446
19447    cx.assert_state_with_diff(both_hunks_expanded.clone());
19448
19449    let hunk_ranges = cx.update_editor(|editor, window, cx| {
19450        let snapshot = editor.snapshot(window, cx);
19451        let hunks = editor
19452            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19453            .collect::<Vec<_>>();
19454        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
19455        let buffer_id = hunks[0].buffer_id;
19456        hunks
19457            .into_iter()
19458            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
19459            .collect::<Vec<_>>()
19460    });
19461    assert_eq!(hunk_ranges.len(), 2);
19462
19463    cx.update_editor(|editor, _, cx| {
19464        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19465    });
19466    executor.run_until_parked();
19467
19468    let second_hunk_expanded = r#"
19469          ˇA
19470          b
19471        - c
19472        + C
19473        "#
19474    .unindent();
19475
19476    cx.assert_state_with_diff(second_hunk_expanded);
19477
19478    cx.update_editor(|editor, _, cx| {
19479        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19480    });
19481    executor.run_until_parked();
19482
19483    cx.assert_state_with_diff(both_hunks_expanded.clone());
19484
19485    cx.update_editor(|editor, _, cx| {
19486        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
19487    });
19488    executor.run_until_parked();
19489
19490    let first_hunk_expanded = r#"
19491        - a
19492        + ˇA
19493          b
19494          C
19495        "#
19496    .unindent();
19497
19498    cx.assert_state_with_diff(first_hunk_expanded);
19499
19500    cx.update_editor(|editor, _, cx| {
19501        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
19502    });
19503    executor.run_until_parked();
19504
19505    cx.assert_state_with_diff(both_hunks_expanded);
19506
19507    cx.set_state(
19508        &r#"
19509        ˇA
19510        b
19511        "#
19512        .unindent(),
19513    );
19514    cx.run_until_parked();
19515
19516    // TODO this cursor position seems bad
19517    cx.assert_state_with_diff(
19518        r#"
19519        - ˇa
19520        + A
19521          b
19522        "#
19523        .unindent(),
19524    );
19525
19526    cx.update_editor(|editor, window, cx| {
19527        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19528    });
19529
19530    cx.assert_state_with_diff(
19531        r#"
19532            - ˇa
19533            + A
19534              b
19535            - c
19536            "#
19537        .unindent(),
19538    );
19539
19540    let hunk_ranges = cx.update_editor(|editor, window, cx| {
19541        let snapshot = editor.snapshot(window, cx);
19542        let hunks = editor
19543            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19544            .collect::<Vec<_>>();
19545        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
19546        let buffer_id = hunks[0].buffer_id;
19547        hunks
19548            .into_iter()
19549            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
19550            .collect::<Vec<_>>()
19551    });
19552    assert_eq!(hunk_ranges.len(), 2);
19553
19554    cx.update_editor(|editor, _, cx| {
19555        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
19556    });
19557    executor.run_until_parked();
19558
19559    cx.assert_state_with_diff(
19560        r#"
19561        - ˇa
19562        + A
19563          b
19564        "#
19565        .unindent(),
19566    );
19567}
19568
19569#[gpui::test]
19570async fn test_toggle_deletion_hunk_at_start_of_file(
19571    executor: BackgroundExecutor,
19572    cx: &mut TestAppContext,
19573) {
19574    init_test(cx, |_| {});
19575    let mut cx = EditorTestContext::new(cx).await;
19576
19577    let diff_base = r#"
19578        a
19579        b
19580        c
19581        "#
19582    .unindent();
19583
19584    cx.set_state(
19585        &r#"
19586        ˇb
19587        c
19588        "#
19589        .unindent(),
19590    );
19591    cx.set_head_text(&diff_base);
19592    cx.update_editor(|editor, window, cx| {
19593        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19594    });
19595    executor.run_until_parked();
19596
19597    let hunk_expanded = r#"
19598        - a
19599          ˇb
19600          c
19601        "#
19602    .unindent();
19603
19604    cx.assert_state_with_diff(hunk_expanded.clone());
19605
19606    let hunk_ranges = cx.update_editor(|editor, window, cx| {
19607        let snapshot = editor.snapshot(window, cx);
19608        let hunks = editor
19609            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19610            .collect::<Vec<_>>();
19611        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
19612        let buffer_id = hunks[0].buffer_id;
19613        hunks
19614            .into_iter()
19615            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
19616            .collect::<Vec<_>>()
19617    });
19618    assert_eq!(hunk_ranges.len(), 1);
19619
19620    cx.update_editor(|editor, _, cx| {
19621        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19622    });
19623    executor.run_until_parked();
19624
19625    let hunk_collapsed = r#"
19626          ˇb
19627          c
19628        "#
19629    .unindent();
19630
19631    cx.assert_state_with_diff(hunk_collapsed);
19632
19633    cx.update_editor(|editor, _, cx| {
19634        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19635    });
19636    executor.run_until_parked();
19637
19638    cx.assert_state_with_diff(hunk_expanded.clone());
19639}
19640
19641#[gpui::test]
19642async fn test_display_diff_hunks(cx: &mut TestAppContext) {
19643    init_test(cx, |_| {});
19644
19645    let fs = FakeFs::new(cx.executor());
19646    fs.insert_tree(
19647        path!("/test"),
19648        json!({
19649            ".git": {},
19650            "file-1": "ONE\n",
19651            "file-2": "TWO\n",
19652            "file-3": "THREE\n",
19653        }),
19654    )
19655    .await;
19656
19657    fs.set_head_for_repo(
19658        path!("/test/.git").as_ref(),
19659        &[
19660            ("file-1".into(), "one\n".into()),
19661            ("file-2".into(), "two\n".into()),
19662            ("file-3".into(), "three\n".into()),
19663        ],
19664        "deadbeef",
19665    );
19666
19667    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
19668    let mut buffers = vec![];
19669    for i in 1..=3 {
19670        let buffer = project
19671            .update(cx, |project, cx| {
19672                let path = format!(path!("/test/file-{}"), i);
19673                project.open_local_buffer(path, cx)
19674            })
19675            .await
19676            .unwrap();
19677        buffers.push(buffer);
19678    }
19679
19680    let multibuffer = cx.new(|cx| {
19681        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
19682        multibuffer.set_all_diff_hunks_expanded(cx);
19683        for buffer in &buffers {
19684            let snapshot = buffer.read(cx).snapshot();
19685            multibuffer.set_excerpts_for_path(
19686                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
19687                buffer.clone(),
19688                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
19689                DEFAULT_MULTIBUFFER_CONTEXT,
19690                cx,
19691            );
19692        }
19693        multibuffer
19694    });
19695
19696    let editor = cx.add_window(|window, cx| {
19697        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
19698    });
19699    cx.run_until_parked();
19700
19701    let snapshot = editor
19702        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19703        .unwrap();
19704    let hunks = snapshot
19705        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
19706        .map(|hunk| match hunk {
19707            DisplayDiffHunk::Unfolded {
19708                display_row_range, ..
19709            } => display_row_range,
19710            DisplayDiffHunk::Folded { .. } => unreachable!(),
19711        })
19712        .collect::<Vec<_>>();
19713    assert_eq!(
19714        hunks,
19715        [
19716            DisplayRow(2)..DisplayRow(4),
19717            DisplayRow(7)..DisplayRow(9),
19718            DisplayRow(12)..DisplayRow(14),
19719        ]
19720    );
19721}
19722
19723#[gpui::test]
19724async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
19725    init_test(cx, |_| {});
19726
19727    let mut cx = EditorTestContext::new(cx).await;
19728    cx.set_head_text(indoc! { "
19729        one
19730        two
19731        three
19732        four
19733        five
19734        "
19735    });
19736    cx.set_index_text(indoc! { "
19737        one
19738        two
19739        three
19740        four
19741        five
19742        "
19743    });
19744    cx.set_state(indoc! {"
19745        one
19746        TWO
19747        ˇTHREE
19748        FOUR
19749        five
19750    "});
19751    cx.run_until_parked();
19752    cx.update_editor(|editor, window, cx| {
19753        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19754    });
19755    cx.run_until_parked();
19756    cx.assert_index_text(Some(indoc! {"
19757        one
19758        TWO
19759        THREE
19760        FOUR
19761        five
19762    "}));
19763    cx.set_state(indoc! { "
19764        one
19765        TWO
19766        ˇTHREE-HUNDRED
19767        FOUR
19768        five
19769    "});
19770    cx.run_until_parked();
19771    cx.update_editor(|editor, window, cx| {
19772        let snapshot = editor.snapshot(window, cx);
19773        let hunks = editor
19774            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19775            .collect::<Vec<_>>();
19776        assert_eq!(hunks.len(), 1);
19777        assert_eq!(
19778            hunks[0].status(),
19779            DiffHunkStatus {
19780                kind: DiffHunkStatusKind::Modified,
19781                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
19782            }
19783        );
19784
19785        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19786    });
19787    cx.run_until_parked();
19788    cx.assert_index_text(Some(indoc! {"
19789        one
19790        TWO
19791        THREE-HUNDRED
19792        FOUR
19793        five
19794    "}));
19795}
19796
19797#[gpui::test]
19798fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
19799    init_test(cx, |_| {});
19800
19801    let editor = cx.add_window(|window, cx| {
19802        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
19803        build_editor(buffer, window, cx)
19804    });
19805
19806    let render_args = Arc::new(Mutex::new(None));
19807    let snapshot = editor
19808        .update(cx, |editor, window, cx| {
19809            let snapshot = editor.buffer().read(cx).snapshot(cx);
19810            let range =
19811                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
19812
19813            struct RenderArgs {
19814                row: MultiBufferRow,
19815                folded: bool,
19816                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
19817            }
19818
19819            let crease = Crease::inline(
19820                range,
19821                FoldPlaceholder::test(),
19822                {
19823                    let toggle_callback = render_args.clone();
19824                    move |row, folded, callback, _window, _cx| {
19825                        *toggle_callback.lock() = Some(RenderArgs {
19826                            row,
19827                            folded,
19828                            callback,
19829                        });
19830                        div()
19831                    }
19832                },
19833                |_row, _folded, _window, _cx| div(),
19834            );
19835
19836            editor.insert_creases(Some(crease), cx);
19837            let snapshot = editor.snapshot(window, cx);
19838            let _div =
19839                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
19840            snapshot
19841        })
19842        .unwrap();
19843
19844    let render_args = render_args.lock().take().unwrap();
19845    assert_eq!(render_args.row, MultiBufferRow(1));
19846    assert!(!render_args.folded);
19847    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
19848
19849    cx.update_window(*editor, |_, window, cx| {
19850        (render_args.callback)(true, window, cx)
19851    })
19852    .unwrap();
19853    let snapshot = editor
19854        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19855        .unwrap();
19856    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
19857
19858    cx.update_window(*editor, |_, window, cx| {
19859        (render_args.callback)(false, 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
19868#[gpui::test]
19869async fn test_input_text(cx: &mut TestAppContext) {
19870    init_test(cx, |_| {});
19871    let mut cx = EditorTestContext::new(cx).await;
19872
19873    cx.set_state(
19874        &r#"ˇone
19875        two
19876
19877        three
19878        fourˇ
19879        five
19880
19881        siˇx"#
19882            .unindent(),
19883    );
19884
19885    cx.dispatch_action(HandleInput(String::new()));
19886    cx.assert_editor_state(
19887        &r#"ˇone
19888        two
19889
19890        three
19891        fourˇ
19892        five
19893
19894        siˇx"#
19895            .unindent(),
19896    );
19897
19898    cx.dispatch_action(HandleInput("AAAA".to_string()));
19899    cx.assert_editor_state(
19900        &r#"AAAAˇone
19901        two
19902
19903        three
19904        fourAAAAˇ
19905        five
19906
19907        siAAAAˇx"#
19908            .unindent(),
19909    );
19910}
19911
19912#[gpui::test]
19913async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
19914    init_test(cx, |_| {});
19915
19916    let mut cx = EditorTestContext::new(cx).await;
19917    cx.set_state(
19918        r#"let foo = 1;
19919let foo = 2;
19920let foo = 3;
19921let fooˇ = 4;
19922let foo = 5;
19923let foo = 6;
19924let foo = 7;
19925let foo = 8;
19926let foo = 9;
19927let foo = 10;
19928let foo = 11;
19929let foo = 12;
19930let foo = 13;
19931let foo = 14;
19932let foo = 15;"#,
19933    );
19934
19935    cx.update_editor(|e, window, cx| {
19936        assert_eq!(
19937            e.next_scroll_position,
19938            NextScrollCursorCenterTopBottom::Center,
19939            "Default next scroll direction is center",
19940        );
19941
19942        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19943        assert_eq!(
19944            e.next_scroll_position,
19945            NextScrollCursorCenterTopBottom::Top,
19946            "After center, next scroll direction should be top",
19947        );
19948
19949        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19950        assert_eq!(
19951            e.next_scroll_position,
19952            NextScrollCursorCenterTopBottom::Bottom,
19953            "After top, next scroll direction should be bottom",
19954        );
19955
19956        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19957        assert_eq!(
19958            e.next_scroll_position,
19959            NextScrollCursorCenterTopBottom::Center,
19960            "After bottom, scrolling should start over",
19961        );
19962
19963        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19964        assert_eq!(
19965            e.next_scroll_position,
19966            NextScrollCursorCenterTopBottom::Top,
19967            "Scrolling continues if retriggered fast enough"
19968        );
19969    });
19970
19971    cx.executor()
19972        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
19973    cx.executor().run_until_parked();
19974    cx.update_editor(|e, _, _| {
19975        assert_eq!(
19976            e.next_scroll_position,
19977            NextScrollCursorCenterTopBottom::Center,
19978            "If scrolling is not triggered fast enough, it should reset"
19979        );
19980    });
19981}
19982
19983#[gpui::test]
19984async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
19985    init_test(cx, |_| {});
19986    let mut cx = EditorLspTestContext::new_rust(
19987        lsp::ServerCapabilities {
19988            definition_provider: Some(lsp::OneOf::Left(true)),
19989            references_provider: Some(lsp::OneOf::Left(true)),
19990            ..lsp::ServerCapabilities::default()
19991        },
19992        cx,
19993    )
19994    .await;
19995
19996    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
19997        let go_to_definition = cx
19998            .lsp
19999            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
20000                move |params, _| async move {
20001                    if empty_go_to_definition {
20002                        Ok(None)
20003                    } else {
20004                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
20005                            uri: params.text_document_position_params.text_document.uri,
20006                            range: lsp::Range::new(
20007                                lsp::Position::new(4, 3),
20008                                lsp::Position::new(4, 6),
20009                            ),
20010                        })))
20011                    }
20012                },
20013            );
20014        let references = cx
20015            .lsp
20016            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
20017                Ok(Some(vec![lsp::Location {
20018                    uri: params.text_document_position.text_document.uri,
20019                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
20020                }]))
20021            });
20022        (go_to_definition, references)
20023    };
20024
20025    cx.set_state(
20026        &r#"fn one() {
20027            let mut a = ˇtwo();
20028        }
20029
20030        fn two() {}"#
20031            .unindent(),
20032    );
20033    set_up_lsp_handlers(false, &mut cx);
20034    let navigated = cx
20035        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
20036        .await
20037        .expect("Failed to navigate to definition");
20038    assert_eq!(
20039        navigated,
20040        Navigated::Yes,
20041        "Should have navigated to definition from the GetDefinition response"
20042    );
20043    cx.assert_editor_state(
20044        &r#"fn one() {
20045            let mut a = two();
20046        }
20047
20048        fn «twoˇ»() {}"#
20049            .unindent(),
20050    );
20051
20052    let editors = cx.update_workspace(|workspace, _, cx| {
20053        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
20054    });
20055    cx.update_editor(|_, _, test_editor_cx| {
20056        assert_eq!(
20057            editors.len(),
20058            1,
20059            "Initially, only one, test, editor should be open in the workspace"
20060        );
20061        assert_eq!(
20062            test_editor_cx.entity(),
20063            editors.last().expect("Asserted len is 1").clone()
20064        );
20065    });
20066
20067    set_up_lsp_handlers(true, &mut cx);
20068    let navigated = cx
20069        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
20070        .await
20071        .expect("Failed to navigate to lookup references");
20072    assert_eq!(
20073        navigated,
20074        Navigated::Yes,
20075        "Should have navigated to references as a fallback after empty GoToDefinition response"
20076    );
20077    // We should not change the selections in the existing file,
20078    // if opening another milti buffer with the references
20079    cx.assert_editor_state(
20080        &r#"fn one() {
20081            let mut a = two();
20082        }
20083
20084        fn «twoˇ»() {}"#
20085            .unindent(),
20086    );
20087    let editors = cx.update_workspace(|workspace, _, cx| {
20088        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
20089    });
20090    cx.update_editor(|_, _, test_editor_cx| {
20091        assert_eq!(
20092            editors.len(),
20093            2,
20094            "After falling back to references search, we open a new editor with the results"
20095        );
20096        let references_fallback_text = editors
20097            .into_iter()
20098            .find(|new_editor| *new_editor != test_editor_cx.entity())
20099            .expect("Should have one non-test editor now")
20100            .read(test_editor_cx)
20101            .text(test_editor_cx);
20102        assert_eq!(
20103            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
20104            "Should use the range from the references response and not the GoToDefinition one"
20105        );
20106    });
20107}
20108
20109#[gpui::test]
20110async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
20111    init_test(cx, |_| {});
20112    cx.update(|cx| {
20113        let mut editor_settings = EditorSettings::get_global(cx).clone();
20114        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
20115        EditorSettings::override_global(editor_settings, cx);
20116    });
20117    let mut cx = EditorLspTestContext::new_rust(
20118        lsp::ServerCapabilities {
20119            definition_provider: Some(lsp::OneOf::Left(true)),
20120            references_provider: Some(lsp::OneOf::Left(true)),
20121            ..lsp::ServerCapabilities::default()
20122        },
20123        cx,
20124    )
20125    .await;
20126    let original_state = r#"fn one() {
20127        let mut a = ˇtwo();
20128    }
20129
20130    fn two() {}"#
20131        .unindent();
20132    cx.set_state(&original_state);
20133
20134    let mut go_to_definition = cx
20135        .lsp
20136        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
20137            move |_, _| async move { Ok(None) },
20138        );
20139    let _references = cx
20140        .lsp
20141        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
20142            panic!("Should not call for references with no go to definition fallback")
20143        });
20144
20145    let navigated = cx
20146        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
20147        .await
20148        .expect("Failed to navigate to lookup references");
20149    go_to_definition
20150        .next()
20151        .await
20152        .expect("Should have called the go_to_definition handler");
20153
20154    assert_eq!(
20155        navigated,
20156        Navigated::No,
20157        "Should have navigated to references as a fallback after empty GoToDefinition response"
20158    );
20159    cx.assert_editor_state(&original_state);
20160    let editors = cx.update_workspace(|workspace, _, cx| {
20161        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
20162    });
20163    cx.update_editor(|_, _, _| {
20164        assert_eq!(
20165            editors.len(),
20166            1,
20167            "After unsuccessful fallback, no other editor should have been opened"
20168        );
20169    });
20170}
20171
20172#[gpui::test]
20173async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
20174    init_test(cx, |_| {});
20175
20176    let language = Arc::new(Language::new(
20177        LanguageConfig::default(),
20178        Some(tree_sitter_rust::LANGUAGE.into()),
20179    ));
20180
20181    let text = r#"
20182        #[cfg(test)]
20183        mod tests() {
20184            #[test]
20185            fn runnable_1() {
20186                let a = 1;
20187            }
20188
20189            #[test]
20190            fn runnable_2() {
20191                let a = 1;
20192                let b = 2;
20193            }
20194        }
20195    "#
20196    .unindent();
20197
20198    let fs = FakeFs::new(cx.executor());
20199    fs.insert_file("/file.rs", Default::default()).await;
20200
20201    let project = Project::test(fs, ["/a".as_ref()], cx).await;
20202    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20203    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20204    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
20205    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
20206
20207    let editor = cx.new_window_entity(|window, cx| {
20208        Editor::new(
20209            EditorMode::full(),
20210            multi_buffer,
20211            Some(project.clone()),
20212            window,
20213            cx,
20214        )
20215    });
20216
20217    editor.update_in(cx, |editor, window, cx| {
20218        let snapshot = editor.buffer().read(cx).snapshot(cx);
20219        editor.tasks.insert(
20220            (buffer.read(cx).remote_id(), 3),
20221            RunnableTasks {
20222                templates: vec![],
20223                offset: snapshot.anchor_before(43),
20224                column: 0,
20225                extra_variables: HashMap::default(),
20226                context_range: BufferOffset(43)..BufferOffset(85),
20227            },
20228        );
20229        editor.tasks.insert(
20230            (buffer.read(cx).remote_id(), 8),
20231            RunnableTasks {
20232                templates: vec![],
20233                offset: snapshot.anchor_before(86),
20234                column: 0,
20235                extra_variables: HashMap::default(),
20236                context_range: BufferOffset(86)..BufferOffset(191),
20237            },
20238        );
20239
20240        // Test finding task when cursor is inside function body
20241        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20242            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
20243        });
20244        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
20245        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
20246
20247        // Test finding task when cursor is on function name
20248        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20249            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
20250        });
20251        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
20252        assert_eq!(row, 8, "Should find task when cursor is on function name");
20253    });
20254}
20255
20256#[gpui::test]
20257async fn test_folding_buffers(cx: &mut TestAppContext) {
20258    init_test(cx, |_| {});
20259
20260    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
20261    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
20262    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
20263
20264    let fs = FakeFs::new(cx.executor());
20265    fs.insert_tree(
20266        path!("/a"),
20267        json!({
20268            "first.rs": sample_text_1,
20269            "second.rs": sample_text_2,
20270            "third.rs": sample_text_3,
20271        }),
20272    )
20273    .await;
20274    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20275    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20276    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20277    let worktree = project.update(cx, |project, cx| {
20278        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
20279        assert_eq!(worktrees.len(), 1);
20280        worktrees.pop().unwrap()
20281    });
20282    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
20283
20284    let buffer_1 = project
20285        .update(cx, |project, cx| {
20286            project.open_buffer((worktree_id, "first.rs"), cx)
20287        })
20288        .await
20289        .unwrap();
20290    let buffer_2 = project
20291        .update(cx, |project, cx| {
20292            project.open_buffer((worktree_id, "second.rs"), cx)
20293        })
20294        .await
20295        .unwrap();
20296    let buffer_3 = project
20297        .update(cx, |project, cx| {
20298            project.open_buffer((worktree_id, "third.rs"), cx)
20299        })
20300        .await
20301        .unwrap();
20302
20303    let multi_buffer = cx.new(|cx| {
20304        let mut multi_buffer = MultiBuffer::new(ReadWrite);
20305        multi_buffer.push_excerpts(
20306            buffer_1.clone(),
20307            [
20308                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20309                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20310                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20311            ],
20312            cx,
20313        );
20314        multi_buffer.push_excerpts(
20315            buffer_2.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_3.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
20333    });
20334    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20335        Editor::new(
20336            EditorMode::full(),
20337            multi_buffer.clone(),
20338            Some(project.clone()),
20339            window,
20340            cx,
20341        )
20342    });
20343
20344    assert_eq!(
20345        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20346        "\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",
20347    );
20348
20349    multi_buffer_editor.update(cx, |editor, cx| {
20350        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
20351    });
20352    assert_eq!(
20353        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20354        "\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",
20355        "After folding the first buffer, its text should not be displayed"
20356    );
20357
20358    multi_buffer_editor.update(cx, |editor, cx| {
20359        editor.fold_buffer(buffer_2.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\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
20364        "After folding the second buffer, its text should not be displayed"
20365    );
20366
20367    multi_buffer_editor.update(cx, |editor, cx| {
20368        editor.fold_buffer(buffer_3.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",
20373        "After folding the third buffer, its text should not be displayed"
20374    );
20375
20376    // Emulate selection inside the fold logic, that should work
20377    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20378        editor
20379            .snapshot(window, cx)
20380            .next_line_boundary(Point::new(0, 4));
20381    });
20382
20383    multi_buffer_editor.update(cx, |editor, cx| {
20384        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
20385    });
20386    assert_eq!(
20387        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20388        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
20389        "After unfolding the second buffer, its text should be displayed"
20390    );
20391
20392    // Typing inside of buffer 1 causes that buffer to be unfolded.
20393    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20394        assert_eq!(
20395            multi_buffer
20396                .read(cx)
20397                .snapshot(cx)
20398                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
20399                .collect::<String>(),
20400            "bbbb"
20401        );
20402        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
20403            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
20404        });
20405        editor.handle_input("B", window, cx);
20406    });
20407
20408    assert_eq!(
20409        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20410        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
20411        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
20412    );
20413
20414    multi_buffer_editor.update(cx, |editor, cx| {
20415        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
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\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
20420        "After unfolding the all buffers, all original text should be displayed"
20421    );
20422}
20423
20424#[gpui::test]
20425async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
20426    init_test(cx, |_| {});
20427
20428    let sample_text_1 = "1111\n2222\n3333".to_string();
20429    let sample_text_2 = "4444\n5555\n6666".to_string();
20430    let sample_text_3 = "7777\n8888\n9999".to_string();
20431
20432    let fs = FakeFs::new(cx.executor());
20433    fs.insert_tree(
20434        path!("/a"),
20435        json!({
20436            "first.rs": sample_text_1,
20437            "second.rs": sample_text_2,
20438            "third.rs": sample_text_3,
20439        }),
20440    )
20441    .await;
20442    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20443    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20444    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20445    let worktree = project.update(cx, |project, cx| {
20446        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
20447        assert_eq!(worktrees.len(), 1);
20448        worktrees.pop().unwrap()
20449    });
20450    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
20451
20452    let buffer_1 = project
20453        .update(cx, |project, cx| {
20454            project.open_buffer((worktree_id, "first.rs"), cx)
20455        })
20456        .await
20457        .unwrap();
20458    let buffer_2 = project
20459        .update(cx, |project, cx| {
20460            project.open_buffer((worktree_id, "second.rs"), cx)
20461        })
20462        .await
20463        .unwrap();
20464    let buffer_3 = project
20465        .update(cx, |project, cx| {
20466            project.open_buffer((worktree_id, "third.rs"), cx)
20467        })
20468        .await
20469        .unwrap();
20470
20471    let multi_buffer = cx.new(|cx| {
20472        let mut multi_buffer = MultiBuffer::new(ReadWrite);
20473        multi_buffer.push_excerpts(
20474            buffer_1.clone(),
20475            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
20476            cx,
20477        );
20478        multi_buffer.push_excerpts(
20479            buffer_2.clone(),
20480            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
20481            cx,
20482        );
20483        multi_buffer.push_excerpts(
20484            buffer_3.clone(),
20485            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
20486            cx,
20487        );
20488        multi_buffer
20489    });
20490
20491    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20492        Editor::new(
20493            EditorMode::full(),
20494            multi_buffer,
20495            Some(project.clone()),
20496            window,
20497            cx,
20498        )
20499    });
20500
20501    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
20502    assert_eq!(
20503        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20504        full_text,
20505    );
20506
20507    multi_buffer_editor.update(cx, |editor, cx| {
20508        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
20509    });
20510    assert_eq!(
20511        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20512        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
20513        "After folding the first buffer, its text should not be displayed"
20514    );
20515
20516    multi_buffer_editor.update(cx, |editor, cx| {
20517        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
20518    });
20519
20520    assert_eq!(
20521        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20522        "\n\n\n\n\n\n7777\n8888\n9999",
20523        "After folding the second buffer, its text should not be displayed"
20524    );
20525
20526    multi_buffer_editor.update(cx, |editor, cx| {
20527        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
20528    });
20529    assert_eq!(
20530        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20531        "\n\n\n\n\n",
20532        "After folding the third buffer, its text should not be displayed"
20533    );
20534
20535    multi_buffer_editor.update(cx, |editor, cx| {
20536        editor.unfold_buffer(buffer_2.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\n4444\n5555\n6666\n\n",
20541        "After unfolding the second buffer, its text should be displayed"
20542    );
20543
20544    multi_buffer_editor.update(cx, |editor, cx| {
20545        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
20546    });
20547    assert_eq!(
20548        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20549        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
20550        "After unfolding the first buffer, its text should be displayed"
20551    );
20552
20553    multi_buffer_editor.update(cx, |editor, cx| {
20554        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
20555    });
20556    assert_eq!(
20557        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20558        full_text,
20559        "After unfolding all buffers, all original text should be displayed"
20560    );
20561}
20562
20563#[gpui::test]
20564async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
20565    init_test(cx, |_| {});
20566
20567    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
20568
20569    let fs = FakeFs::new(cx.executor());
20570    fs.insert_tree(
20571        path!("/a"),
20572        json!({
20573            "main.rs": sample_text,
20574        }),
20575    )
20576    .await;
20577    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20578    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20579    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20580    let worktree = project.update(cx, |project, cx| {
20581        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
20582        assert_eq!(worktrees.len(), 1);
20583        worktrees.pop().unwrap()
20584    });
20585    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
20586
20587    let buffer_1 = project
20588        .update(cx, |project, cx| {
20589            project.open_buffer((worktree_id, "main.rs"), cx)
20590        })
20591        .await
20592        .unwrap();
20593
20594    let multi_buffer = cx.new(|cx| {
20595        let mut multi_buffer = MultiBuffer::new(ReadWrite);
20596        multi_buffer.push_excerpts(
20597            buffer_1.clone(),
20598            [ExcerptRange::new(
20599                Point::new(0, 0)
20600                    ..Point::new(
20601                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
20602                        0,
20603                    ),
20604            )],
20605            cx,
20606        );
20607        multi_buffer
20608    });
20609    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20610        Editor::new(
20611            EditorMode::full(),
20612            multi_buffer,
20613            Some(project.clone()),
20614            window,
20615            cx,
20616        )
20617    });
20618
20619    let selection_range = Point::new(1, 0)..Point::new(2, 0);
20620    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20621        enum TestHighlight {}
20622        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
20623        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
20624        editor.highlight_text::<TestHighlight>(
20625            vec![highlight_range.clone()],
20626            HighlightStyle::color(Hsla::green()),
20627            cx,
20628        );
20629        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20630            s.select_ranges(Some(highlight_range))
20631        });
20632    });
20633
20634    let full_text = format!("\n\n{sample_text}");
20635    assert_eq!(
20636        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20637        full_text,
20638    );
20639}
20640
20641#[gpui::test]
20642async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
20643    init_test(cx, |_| {});
20644    cx.update(|cx| {
20645        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
20646            "keymaps/default-linux.json",
20647            cx,
20648        )
20649        .unwrap();
20650        cx.bind_keys(default_key_bindings);
20651    });
20652
20653    let (editor, cx) = cx.add_window_view(|window, cx| {
20654        let multi_buffer = MultiBuffer::build_multi(
20655            [
20656                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
20657                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
20658                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
20659                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
20660            ],
20661            cx,
20662        );
20663        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
20664
20665        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
20666        // fold all but the second buffer, so that we test navigating between two
20667        // adjacent folded buffers, as well as folded buffers at the start and
20668        // end the multibuffer
20669        editor.fold_buffer(buffer_ids[0], cx);
20670        editor.fold_buffer(buffer_ids[2], cx);
20671        editor.fold_buffer(buffer_ids[3], cx);
20672
20673        editor
20674    });
20675    cx.simulate_resize(size(px(1000.), px(1000.)));
20676
20677    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
20678    cx.assert_excerpts_with_selections(indoc! {"
20679        [EXCERPT]
20680        ˇ[FOLDED]
20681        [EXCERPT]
20682        a1
20683        b1
20684        [EXCERPT]
20685        [FOLDED]
20686        [EXCERPT]
20687        [FOLDED]
20688        "
20689    });
20690    cx.simulate_keystroke("down");
20691    cx.assert_excerpts_with_selections(indoc! {"
20692        [EXCERPT]
20693        [FOLDED]
20694        [EXCERPT]
20695        ˇa1
20696        b1
20697        [EXCERPT]
20698        [FOLDED]
20699        [EXCERPT]
20700        [FOLDED]
20701        "
20702    });
20703    cx.simulate_keystroke("down");
20704    cx.assert_excerpts_with_selections(indoc! {"
20705        [EXCERPT]
20706        [FOLDED]
20707        [EXCERPT]
20708        a1
20709        ˇb1
20710        [EXCERPT]
20711        [FOLDED]
20712        [EXCERPT]
20713        [FOLDED]
20714        "
20715    });
20716    cx.simulate_keystroke("down");
20717    cx.assert_excerpts_with_selections(indoc! {"
20718        [EXCERPT]
20719        [FOLDED]
20720        [EXCERPT]
20721        a1
20722        b1
20723        ˇ[EXCERPT]
20724        [FOLDED]
20725        [EXCERPT]
20726        [FOLDED]
20727        "
20728    });
20729    cx.simulate_keystroke("down");
20730    cx.assert_excerpts_with_selections(indoc! {"
20731        [EXCERPT]
20732        [FOLDED]
20733        [EXCERPT]
20734        a1
20735        b1
20736        [EXCERPT]
20737        ˇ[FOLDED]
20738        [EXCERPT]
20739        [FOLDED]
20740        "
20741    });
20742    for _ in 0..5 {
20743        cx.simulate_keystroke("down");
20744        cx.assert_excerpts_with_selections(indoc! {"
20745            [EXCERPT]
20746            [FOLDED]
20747            [EXCERPT]
20748            a1
20749            b1
20750            [EXCERPT]
20751            [FOLDED]
20752            [EXCERPT]
20753            ˇ[FOLDED]
20754            "
20755        });
20756    }
20757
20758    cx.simulate_keystroke("up");
20759    cx.assert_excerpts_with_selections(indoc! {"
20760        [EXCERPT]
20761        [FOLDED]
20762        [EXCERPT]
20763        a1
20764        b1
20765        [EXCERPT]
20766        ˇ[FOLDED]
20767        [EXCERPT]
20768        [FOLDED]
20769        "
20770    });
20771    cx.simulate_keystroke("up");
20772    cx.assert_excerpts_with_selections(indoc! {"
20773        [EXCERPT]
20774        [FOLDED]
20775        [EXCERPT]
20776        a1
20777        b1
20778        ˇ[EXCERPT]
20779        [FOLDED]
20780        [EXCERPT]
20781        [FOLDED]
20782        "
20783    });
20784    cx.simulate_keystroke("up");
20785    cx.assert_excerpts_with_selections(indoc! {"
20786        [EXCERPT]
20787        [FOLDED]
20788        [EXCERPT]
20789        a1
20790        ˇb1
20791        [EXCERPT]
20792        [FOLDED]
20793        [EXCERPT]
20794        [FOLDED]
20795        "
20796    });
20797    cx.simulate_keystroke("up");
20798    cx.assert_excerpts_with_selections(indoc! {"
20799        [EXCERPT]
20800        [FOLDED]
20801        [EXCERPT]
20802        ˇa1
20803        b1
20804        [EXCERPT]
20805        [FOLDED]
20806        [EXCERPT]
20807        [FOLDED]
20808        "
20809    });
20810    for _ in 0..5 {
20811        cx.simulate_keystroke("up");
20812        cx.assert_excerpts_with_selections(indoc! {"
20813            [EXCERPT]
20814            ˇ[FOLDED]
20815            [EXCERPT]
20816            a1
20817            b1
20818            [EXCERPT]
20819            [FOLDED]
20820            [EXCERPT]
20821            [FOLDED]
20822            "
20823        });
20824    }
20825}
20826
20827#[gpui::test]
20828async fn test_edit_prediction_text(cx: &mut TestAppContext) {
20829    init_test(cx, |_| {});
20830
20831    // Simple insertion
20832    assert_highlighted_edits(
20833        "Hello, world!",
20834        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
20835        true,
20836        cx,
20837        |highlighted_edits, cx| {
20838            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
20839            assert_eq!(highlighted_edits.highlights.len(), 1);
20840            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
20841            assert_eq!(
20842                highlighted_edits.highlights[0].1.background_color,
20843                Some(cx.theme().status().created_background)
20844            );
20845        },
20846    )
20847    .await;
20848
20849    // Replacement
20850    assert_highlighted_edits(
20851        "This is a test.",
20852        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
20853        false,
20854        cx,
20855        |highlighted_edits, cx| {
20856            assert_eq!(highlighted_edits.text, "That is a test.");
20857            assert_eq!(highlighted_edits.highlights.len(), 1);
20858            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
20859            assert_eq!(
20860                highlighted_edits.highlights[0].1.background_color,
20861                Some(cx.theme().status().created_background)
20862            );
20863        },
20864    )
20865    .await;
20866
20867    // Multiple edits
20868    assert_highlighted_edits(
20869        "Hello, world!",
20870        vec![
20871            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
20872            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
20873        ],
20874        false,
20875        cx,
20876        |highlighted_edits, cx| {
20877            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
20878            assert_eq!(highlighted_edits.highlights.len(), 2);
20879            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
20880            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
20881            assert_eq!(
20882                highlighted_edits.highlights[0].1.background_color,
20883                Some(cx.theme().status().created_background)
20884            );
20885            assert_eq!(
20886                highlighted_edits.highlights[1].1.background_color,
20887                Some(cx.theme().status().created_background)
20888            );
20889        },
20890    )
20891    .await;
20892
20893    // Multiple lines with edits
20894    assert_highlighted_edits(
20895        "First line\nSecond line\nThird line\nFourth line",
20896        vec![
20897            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
20898            (
20899                Point::new(2, 0)..Point::new(2, 10),
20900                "New third line".to_string(),
20901            ),
20902            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
20903        ],
20904        false,
20905        cx,
20906        |highlighted_edits, cx| {
20907            assert_eq!(
20908                highlighted_edits.text,
20909                "Second modified\nNew third line\nFourth updated line"
20910            );
20911            assert_eq!(highlighted_edits.highlights.len(), 3);
20912            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
20913            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
20914            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
20915            for highlight in &highlighted_edits.highlights {
20916                assert_eq!(
20917                    highlight.1.background_color,
20918                    Some(cx.theme().status().created_background)
20919                );
20920            }
20921        },
20922    )
20923    .await;
20924}
20925
20926#[gpui::test]
20927async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
20928    init_test(cx, |_| {});
20929
20930    // Deletion
20931    assert_highlighted_edits(
20932        "Hello, world!",
20933        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
20934        true,
20935        cx,
20936        |highlighted_edits, cx| {
20937            assert_eq!(highlighted_edits.text, "Hello, world!");
20938            assert_eq!(highlighted_edits.highlights.len(), 1);
20939            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
20940            assert_eq!(
20941                highlighted_edits.highlights[0].1.background_color,
20942                Some(cx.theme().status().deleted_background)
20943            );
20944        },
20945    )
20946    .await;
20947
20948    // Insertion
20949    assert_highlighted_edits(
20950        "Hello, world!",
20951        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
20952        true,
20953        cx,
20954        |highlighted_edits, cx| {
20955            assert_eq!(highlighted_edits.highlights.len(), 1);
20956            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
20957            assert_eq!(
20958                highlighted_edits.highlights[0].1.background_color,
20959                Some(cx.theme().status().created_background)
20960            );
20961        },
20962    )
20963    .await;
20964}
20965
20966async fn assert_highlighted_edits(
20967    text: &str,
20968    edits: Vec<(Range<Point>, String)>,
20969    include_deletions: bool,
20970    cx: &mut TestAppContext,
20971    assertion_fn: impl Fn(HighlightedText, &App),
20972) {
20973    let window = cx.add_window(|window, cx| {
20974        let buffer = MultiBuffer::build_simple(text, cx);
20975        Editor::new(EditorMode::full(), buffer, None, window, cx)
20976    });
20977    let cx = &mut VisualTestContext::from_window(*window, cx);
20978
20979    let (buffer, snapshot) = window
20980        .update(cx, |editor, _window, cx| {
20981            (
20982                editor.buffer().clone(),
20983                editor.buffer().read(cx).snapshot(cx),
20984            )
20985        })
20986        .unwrap();
20987
20988    let edits = edits
20989        .into_iter()
20990        .map(|(range, edit)| {
20991            (
20992                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
20993                edit,
20994            )
20995        })
20996        .collect::<Vec<_>>();
20997
20998    let text_anchor_edits = edits
20999        .clone()
21000        .into_iter()
21001        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
21002        .collect::<Vec<_>>();
21003
21004    let edit_preview = window
21005        .update(cx, |_, _window, cx| {
21006            buffer
21007                .read(cx)
21008                .as_singleton()
21009                .unwrap()
21010                .read(cx)
21011                .preview_edits(text_anchor_edits.into(), cx)
21012        })
21013        .unwrap()
21014        .await;
21015
21016    cx.update(|_window, cx| {
21017        let highlighted_edits = edit_prediction_edit_text(
21018            snapshot.as_singleton().unwrap().2,
21019            &edits,
21020            &edit_preview,
21021            include_deletions,
21022            cx,
21023        );
21024        assertion_fn(highlighted_edits, cx)
21025    });
21026}
21027
21028#[track_caller]
21029fn assert_breakpoint(
21030    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
21031    path: &Arc<Path>,
21032    expected: Vec<(u32, Breakpoint)>,
21033) {
21034    if expected.len() == 0usize {
21035        assert!(!breakpoints.contains_key(path), "{}", path.display());
21036    } else {
21037        let mut breakpoint = breakpoints
21038            .get(path)
21039            .unwrap()
21040            .into_iter()
21041            .map(|breakpoint| {
21042                (
21043                    breakpoint.row,
21044                    Breakpoint {
21045                        message: breakpoint.message.clone(),
21046                        state: breakpoint.state,
21047                        condition: breakpoint.condition.clone(),
21048                        hit_condition: breakpoint.hit_condition.clone(),
21049                    },
21050                )
21051            })
21052            .collect::<Vec<_>>();
21053
21054        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
21055
21056        assert_eq!(expected, breakpoint);
21057    }
21058}
21059
21060fn add_log_breakpoint_at_cursor(
21061    editor: &mut Editor,
21062    log_message: &str,
21063    window: &mut Window,
21064    cx: &mut Context<Editor>,
21065) {
21066    let (anchor, bp) = editor
21067        .breakpoints_at_cursors(window, cx)
21068        .first()
21069        .and_then(|(anchor, bp)| {
21070            if let Some(bp) = bp {
21071                Some((*anchor, bp.clone()))
21072            } else {
21073                None
21074            }
21075        })
21076        .unwrap_or_else(|| {
21077            let cursor_position: Point = editor.selections.newest(cx).head();
21078
21079            let breakpoint_position = editor
21080                .snapshot(window, cx)
21081                .display_snapshot
21082                .buffer_snapshot
21083                .anchor_before(Point::new(cursor_position.row, 0));
21084
21085            (breakpoint_position, Breakpoint::new_log(log_message))
21086        });
21087
21088    editor.edit_breakpoint_at_anchor(
21089        anchor,
21090        bp,
21091        BreakpointEditAction::EditLogMessage(log_message.into()),
21092        cx,
21093    );
21094}
21095
21096#[gpui::test]
21097async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
21098    init_test(cx, |_| {});
21099
21100    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
21101    let fs = FakeFs::new(cx.executor());
21102    fs.insert_tree(
21103        path!("/a"),
21104        json!({
21105            "main.rs": sample_text,
21106        }),
21107    )
21108    .await;
21109    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21110    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21111    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21112
21113    let fs = FakeFs::new(cx.executor());
21114    fs.insert_tree(
21115        path!("/a"),
21116        json!({
21117            "main.rs": sample_text,
21118        }),
21119    )
21120    .await;
21121    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21122    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21123    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21124    let worktree_id = workspace
21125        .update(cx, |workspace, _window, cx| {
21126            workspace.project().update(cx, |project, cx| {
21127                project.worktrees(cx).next().unwrap().read(cx).id()
21128            })
21129        })
21130        .unwrap();
21131
21132    let buffer = project
21133        .update(cx, |project, cx| {
21134            project.open_buffer((worktree_id, "main.rs"), cx)
21135        })
21136        .await
21137        .unwrap();
21138
21139    let (editor, cx) = cx.add_window_view(|window, cx| {
21140        Editor::new(
21141            EditorMode::full(),
21142            MultiBuffer::build_from_buffer(buffer, cx),
21143            Some(project.clone()),
21144            window,
21145            cx,
21146        )
21147    });
21148
21149    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
21150    let abs_path = project.read_with(cx, |project, cx| {
21151        project
21152            .absolute_path(&project_path, cx)
21153            .map(|path_buf| Arc::from(path_buf.to_owned()))
21154            .unwrap()
21155    });
21156
21157    // assert we can add breakpoint on the first line
21158    editor.update_in(cx, |editor, window, cx| {
21159        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21160        editor.move_to_end(&MoveToEnd, window, cx);
21161        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21162    });
21163
21164    let breakpoints = editor.update(cx, |editor, cx| {
21165        editor
21166            .breakpoint_store()
21167            .as_ref()
21168            .unwrap()
21169            .read(cx)
21170            .all_source_breakpoints(cx)
21171            .clone()
21172    });
21173
21174    assert_eq!(1, breakpoints.len());
21175    assert_breakpoint(
21176        &breakpoints,
21177        &abs_path,
21178        vec![
21179            (0, Breakpoint::new_standard()),
21180            (3, Breakpoint::new_standard()),
21181        ],
21182    );
21183
21184    editor.update_in(cx, |editor, window, cx| {
21185        editor.move_to_beginning(&MoveToBeginning, window, cx);
21186        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21187    });
21188
21189    let breakpoints = editor.update(cx, |editor, cx| {
21190        editor
21191            .breakpoint_store()
21192            .as_ref()
21193            .unwrap()
21194            .read(cx)
21195            .all_source_breakpoints(cx)
21196            .clone()
21197    });
21198
21199    assert_eq!(1, breakpoints.len());
21200    assert_breakpoint(
21201        &breakpoints,
21202        &abs_path,
21203        vec![(3, Breakpoint::new_standard())],
21204    );
21205
21206    editor.update_in(cx, |editor, window, cx| {
21207        editor.move_to_end(&MoveToEnd, window, cx);
21208        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21209    });
21210
21211    let breakpoints = editor.update(cx, |editor, cx| {
21212        editor
21213            .breakpoint_store()
21214            .as_ref()
21215            .unwrap()
21216            .read(cx)
21217            .all_source_breakpoints(cx)
21218            .clone()
21219    });
21220
21221    assert_eq!(0, breakpoints.len());
21222    assert_breakpoint(&breakpoints, &abs_path, vec![]);
21223}
21224
21225#[gpui::test]
21226async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
21227    init_test(cx, |_| {});
21228
21229    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
21230
21231    let fs = FakeFs::new(cx.executor());
21232    fs.insert_tree(
21233        path!("/a"),
21234        json!({
21235            "main.rs": sample_text,
21236        }),
21237    )
21238    .await;
21239    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21240    let (workspace, cx) =
21241        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21242
21243    let worktree_id = workspace.update(cx, |workspace, cx| {
21244        workspace.project().update(cx, |project, cx| {
21245            project.worktrees(cx).next().unwrap().read(cx).id()
21246        })
21247    });
21248
21249    let buffer = project
21250        .update(cx, |project, cx| {
21251            project.open_buffer((worktree_id, "main.rs"), cx)
21252        })
21253        .await
21254        .unwrap();
21255
21256    let (editor, cx) = cx.add_window_view(|window, cx| {
21257        Editor::new(
21258            EditorMode::full(),
21259            MultiBuffer::build_from_buffer(buffer, cx),
21260            Some(project.clone()),
21261            window,
21262            cx,
21263        )
21264    });
21265
21266    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
21267    let abs_path = project.read_with(cx, |project, cx| {
21268        project
21269            .absolute_path(&project_path, cx)
21270            .map(|path_buf| Arc::from(path_buf.to_owned()))
21271            .unwrap()
21272    });
21273
21274    editor.update_in(cx, |editor, window, cx| {
21275        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
21276    });
21277
21278    let breakpoints = editor.update(cx, |editor, cx| {
21279        editor
21280            .breakpoint_store()
21281            .as_ref()
21282            .unwrap()
21283            .read(cx)
21284            .all_source_breakpoints(cx)
21285            .clone()
21286    });
21287
21288    assert_breakpoint(
21289        &breakpoints,
21290        &abs_path,
21291        vec![(0, Breakpoint::new_log("hello world"))],
21292    );
21293
21294    // Removing a log message from a log breakpoint should remove it
21295    editor.update_in(cx, |editor, window, cx| {
21296        add_log_breakpoint_at_cursor(editor, "", window, cx);
21297    });
21298
21299    let breakpoints = editor.update(cx, |editor, cx| {
21300        editor
21301            .breakpoint_store()
21302            .as_ref()
21303            .unwrap()
21304            .read(cx)
21305            .all_source_breakpoints(cx)
21306            .clone()
21307    });
21308
21309    assert_breakpoint(&breakpoints, &abs_path, vec![]);
21310
21311    editor.update_in(cx, |editor, window, cx| {
21312        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21313        editor.move_to_end(&MoveToEnd, window, cx);
21314        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21315        // Not adding a log message to a standard breakpoint shouldn't remove it
21316        add_log_breakpoint_at_cursor(editor, "", window, cx);
21317    });
21318
21319    let breakpoints = editor.update(cx, |editor, cx| {
21320        editor
21321            .breakpoint_store()
21322            .as_ref()
21323            .unwrap()
21324            .read(cx)
21325            .all_source_breakpoints(cx)
21326            .clone()
21327    });
21328
21329    assert_breakpoint(
21330        &breakpoints,
21331        &abs_path,
21332        vec![
21333            (0, Breakpoint::new_standard()),
21334            (3, Breakpoint::new_standard()),
21335        ],
21336    );
21337
21338    editor.update_in(cx, |editor, window, cx| {
21339        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
21340    });
21341
21342    let breakpoints = editor.update(cx, |editor, cx| {
21343        editor
21344            .breakpoint_store()
21345            .as_ref()
21346            .unwrap()
21347            .read(cx)
21348            .all_source_breakpoints(cx)
21349            .clone()
21350    });
21351
21352    assert_breakpoint(
21353        &breakpoints,
21354        &abs_path,
21355        vec![
21356            (0, Breakpoint::new_standard()),
21357            (3, Breakpoint::new_log("hello world")),
21358        ],
21359    );
21360
21361    editor.update_in(cx, |editor, window, cx| {
21362        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
21363    });
21364
21365    let breakpoints = editor.update(cx, |editor, cx| {
21366        editor
21367            .breakpoint_store()
21368            .as_ref()
21369            .unwrap()
21370            .read(cx)
21371            .all_source_breakpoints(cx)
21372            .clone()
21373    });
21374
21375    assert_breakpoint(
21376        &breakpoints,
21377        &abs_path,
21378        vec![
21379            (0, Breakpoint::new_standard()),
21380            (3, Breakpoint::new_log("hello Earth!!")),
21381        ],
21382    );
21383}
21384
21385/// This also tests that Editor::breakpoint_at_cursor_head is working properly
21386/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
21387/// or when breakpoints were placed out of order. This tests for a regression too
21388#[gpui::test]
21389async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
21390    init_test(cx, |_| {});
21391
21392    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
21393    let fs = FakeFs::new(cx.executor());
21394    fs.insert_tree(
21395        path!("/a"),
21396        json!({
21397            "main.rs": sample_text,
21398        }),
21399    )
21400    .await;
21401    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21402    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21403    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21404
21405    let fs = FakeFs::new(cx.executor());
21406    fs.insert_tree(
21407        path!("/a"),
21408        json!({
21409            "main.rs": sample_text,
21410        }),
21411    )
21412    .await;
21413    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21414    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21415    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21416    let worktree_id = workspace
21417        .update(cx, |workspace, _window, cx| {
21418            workspace.project().update(cx, |project, cx| {
21419                project.worktrees(cx).next().unwrap().read(cx).id()
21420            })
21421        })
21422        .unwrap();
21423
21424    let buffer = project
21425        .update(cx, |project, cx| {
21426            project.open_buffer((worktree_id, "main.rs"), cx)
21427        })
21428        .await
21429        .unwrap();
21430
21431    let (editor, cx) = cx.add_window_view(|window, cx| {
21432        Editor::new(
21433            EditorMode::full(),
21434            MultiBuffer::build_from_buffer(buffer, cx),
21435            Some(project.clone()),
21436            window,
21437            cx,
21438        )
21439    });
21440
21441    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
21442    let abs_path = project.read_with(cx, |project, cx| {
21443        project
21444            .absolute_path(&project_path, cx)
21445            .map(|path_buf| Arc::from(path_buf.to_owned()))
21446            .unwrap()
21447    });
21448
21449    // assert we can add breakpoint on the first line
21450    editor.update_in(cx, |editor, window, cx| {
21451        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21452        editor.move_to_end(&MoveToEnd, window, cx);
21453        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21454        editor.move_up(&MoveUp, window, cx);
21455        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21456    });
21457
21458    let breakpoints = editor.update(cx, |editor, cx| {
21459        editor
21460            .breakpoint_store()
21461            .as_ref()
21462            .unwrap()
21463            .read(cx)
21464            .all_source_breakpoints(cx)
21465            .clone()
21466    });
21467
21468    assert_eq!(1, breakpoints.len());
21469    assert_breakpoint(
21470        &breakpoints,
21471        &abs_path,
21472        vec![
21473            (0, Breakpoint::new_standard()),
21474            (2, Breakpoint::new_standard()),
21475            (3, Breakpoint::new_standard()),
21476        ],
21477    );
21478
21479    editor.update_in(cx, |editor, window, cx| {
21480        editor.move_to_beginning(&MoveToBeginning, window, cx);
21481        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21482        editor.move_to_end(&MoveToEnd, window, cx);
21483        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21484        // Disabling a breakpoint that doesn't exist should do nothing
21485        editor.move_up(&MoveUp, window, cx);
21486        editor.move_up(&MoveUp, window, cx);
21487        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21488    });
21489
21490    let breakpoints = editor.update(cx, |editor, cx| {
21491        editor
21492            .breakpoint_store()
21493            .as_ref()
21494            .unwrap()
21495            .read(cx)
21496            .all_source_breakpoints(cx)
21497            .clone()
21498    });
21499
21500    let disable_breakpoint = {
21501        let mut bp = Breakpoint::new_standard();
21502        bp.state = BreakpointState::Disabled;
21503        bp
21504    };
21505
21506    assert_eq!(1, breakpoints.len());
21507    assert_breakpoint(
21508        &breakpoints,
21509        &abs_path,
21510        vec![
21511            (0, disable_breakpoint.clone()),
21512            (2, Breakpoint::new_standard()),
21513            (3, disable_breakpoint.clone()),
21514        ],
21515    );
21516
21517    editor.update_in(cx, |editor, window, cx| {
21518        editor.move_to_beginning(&MoveToBeginning, window, cx);
21519        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
21520        editor.move_to_end(&MoveToEnd, window, cx);
21521        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
21522        editor.move_up(&MoveUp, window, cx);
21523        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21524    });
21525
21526    let breakpoints = editor.update(cx, |editor, cx| {
21527        editor
21528            .breakpoint_store()
21529            .as_ref()
21530            .unwrap()
21531            .read(cx)
21532            .all_source_breakpoints(cx)
21533            .clone()
21534    });
21535
21536    assert_eq!(1, breakpoints.len());
21537    assert_breakpoint(
21538        &breakpoints,
21539        &abs_path,
21540        vec![
21541            (0, Breakpoint::new_standard()),
21542            (2, disable_breakpoint),
21543            (3, Breakpoint::new_standard()),
21544        ],
21545    );
21546}
21547
21548#[gpui::test]
21549async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
21550    init_test(cx, |_| {});
21551    let capabilities = lsp::ServerCapabilities {
21552        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
21553            prepare_provider: Some(true),
21554            work_done_progress_options: Default::default(),
21555        })),
21556        ..Default::default()
21557    };
21558    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
21559
21560    cx.set_state(indoc! {"
21561        struct Fˇoo {}
21562    "});
21563
21564    cx.update_editor(|editor, _, cx| {
21565        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
21566        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
21567        editor.highlight_background::<DocumentHighlightRead>(
21568            &[highlight_range],
21569            |theme| theme.colors().editor_document_highlight_read_background,
21570            cx,
21571        );
21572    });
21573
21574    let mut prepare_rename_handler = cx
21575        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
21576            move |_, _, _| async move {
21577                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
21578                    start: lsp::Position {
21579                        line: 0,
21580                        character: 7,
21581                    },
21582                    end: lsp::Position {
21583                        line: 0,
21584                        character: 10,
21585                    },
21586                })))
21587            },
21588        );
21589    let prepare_rename_task = cx
21590        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
21591        .expect("Prepare rename was not started");
21592    prepare_rename_handler.next().await.unwrap();
21593    prepare_rename_task.await.expect("Prepare rename failed");
21594
21595    let mut rename_handler =
21596        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
21597            let edit = lsp::TextEdit {
21598                range: lsp::Range {
21599                    start: lsp::Position {
21600                        line: 0,
21601                        character: 7,
21602                    },
21603                    end: lsp::Position {
21604                        line: 0,
21605                        character: 10,
21606                    },
21607                },
21608                new_text: "FooRenamed".to_string(),
21609            };
21610            Ok(Some(lsp::WorkspaceEdit::new(
21611                // Specify the same edit twice
21612                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
21613            )))
21614        });
21615    let rename_task = cx
21616        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
21617        .expect("Confirm rename was not started");
21618    rename_handler.next().await.unwrap();
21619    rename_task.await.expect("Confirm rename failed");
21620    cx.run_until_parked();
21621
21622    // Despite two edits, only one is actually applied as those are identical
21623    cx.assert_editor_state(indoc! {"
21624        struct FooRenamedˇ {}
21625    "});
21626}
21627
21628#[gpui::test]
21629async fn test_rename_without_prepare(cx: &mut TestAppContext) {
21630    init_test(cx, |_| {});
21631    // These capabilities indicate that the server does not support prepare rename.
21632    let capabilities = lsp::ServerCapabilities {
21633        rename_provider: Some(lsp::OneOf::Left(true)),
21634        ..Default::default()
21635    };
21636    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
21637
21638    cx.set_state(indoc! {"
21639        struct Fˇoo {}
21640    "});
21641
21642    cx.update_editor(|editor, _window, cx| {
21643        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
21644        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
21645        editor.highlight_background::<DocumentHighlightRead>(
21646            &[highlight_range],
21647            |theme| theme.colors().editor_document_highlight_read_background,
21648            cx,
21649        );
21650    });
21651
21652    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
21653        .expect("Prepare rename was not started")
21654        .await
21655        .expect("Prepare rename failed");
21656
21657    let mut rename_handler =
21658        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
21659            let edit = lsp::TextEdit {
21660                range: lsp::Range {
21661                    start: lsp::Position {
21662                        line: 0,
21663                        character: 7,
21664                    },
21665                    end: lsp::Position {
21666                        line: 0,
21667                        character: 10,
21668                    },
21669                },
21670                new_text: "FooRenamed".to_string(),
21671            };
21672            Ok(Some(lsp::WorkspaceEdit::new(
21673                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
21674            )))
21675        });
21676    let rename_task = cx
21677        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
21678        .expect("Confirm rename was not started");
21679    rename_handler.next().await.unwrap();
21680    rename_task.await.expect("Confirm rename failed");
21681    cx.run_until_parked();
21682
21683    // Correct range is renamed, as `surrounding_word` is used to find it.
21684    cx.assert_editor_state(indoc! {"
21685        struct FooRenamedˇ {}
21686    "});
21687}
21688
21689#[gpui::test]
21690async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
21691    init_test(cx, |_| {});
21692    let mut cx = EditorTestContext::new(cx).await;
21693
21694    let language = Arc::new(
21695        Language::new(
21696            LanguageConfig::default(),
21697            Some(tree_sitter_html::LANGUAGE.into()),
21698        )
21699        .with_brackets_query(
21700            r#"
21701            ("<" @open "/>" @close)
21702            ("</" @open ">" @close)
21703            ("<" @open ">" @close)
21704            ("\"" @open "\"" @close)
21705            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
21706        "#,
21707        )
21708        .unwrap(),
21709    );
21710    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
21711
21712    cx.set_state(indoc! {"
21713        <span>ˇ</span>
21714    "});
21715    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21716    cx.assert_editor_state(indoc! {"
21717        <span>
21718        ˇ
21719        </span>
21720    "});
21721
21722    cx.set_state(indoc! {"
21723        <span><span></span>ˇ</span>
21724    "});
21725    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21726    cx.assert_editor_state(indoc! {"
21727        <span><span></span>
21728        ˇ</span>
21729    "});
21730
21731    cx.set_state(indoc! {"
21732        <span>ˇ
21733        </span>
21734    "});
21735    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21736    cx.assert_editor_state(indoc! {"
21737        <span>
21738        ˇ
21739        </span>
21740    "});
21741}
21742
21743#[gpui::test(iterations = 10)]
21744async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
21745    init_test(cx, |_| {});
21746
21747    let fs = FakeFs::new(cx.executor());
21748    fs.insert_tree(
21749        path!("/dir"),
21750        json!({
21751            "a.ts": "a",
21752        }),
21753    )
21754    .await;
21755
21756    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
21757    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21758    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21759
21760    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21761    language_registry.add(Arc::new(Language::new(
21762        LanguageConfig {
21763            name: "TypeScript".into(),
21764            matcher: LanguageMatcher {
21765                path_suffixes: vec!["ts".to_string()],
21766                ..Default::default()
21767            },
21768            ..Default::default()
21769        },
21770        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
21771    )));
21772    let mut fake_language_servers = language_registry.register_fake_lsp(
21773        "TypeScript",
21774        FakeLspAdapter {
21775            capabilities: lsp::ServerCapabilities {
21776                code_lens_provider: Some(lsp::CodeLensOptions {
21777                    resolve_provider: Some(true),
21778                }),
21779                execute_command_provider: Some(lsp::ExecuteCommandOptions {
21780                    commands: vec!["_the/command".to_string()],
21781                    ..lsp::ExecuteCommandOptions::default()
21782                }),
21783                ..lsp::ServerCapabilities::default()
21784            },
21785            ..FakeLspAdapter::default()
21786        },
21787    );
21788
21789    let editor = workspace
21790        .update(cx, |workspace, window, cx| {
21791            workspace.open_abs_path(
21792                PathBuf::from(path!("/dir/a.ts")),
21793                OpenOptions::default(),
21794                window,
21795                cx,
21796            )
21797        })
21798        .unwrap()
21799        .await
21800        .unwrap()
21801        .downcast::<Editor>()
21802        .unwrap();
21803    cx.executor().run_until_parked();
21804
21805    let fake_server = fake_language_servers.next().await.unwrap();
21806
21807    let buffer = editor.update(cx, |editor, cx| {
21808        editor
21809            .buffer()
21810            .read(cx)
21811            .as_singleton()
21812            .expect("have opened a single file by path")
21813    });
21814
21815    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
21816    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
21817    drop(buffer_snapshot);
21818    let actions = cx
21819        .update_window(*workspace, |_, window, cx| {
21820            project.code_actions(&buffer, anchor..anchor, window, cx)
21821        })
21822        .unwrap();
21823
21824    fake_server
21825        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
21826            Ok(Some(vec![
21827                lsp::CodeLens {
21828                    range: lsp::Range::default(),
21829                    command: Some(lsp::Command {
21830                        title: "Code lens command".to_owned(),
21831                        command: "_the/command".to_owned(),
21832                        arguments: None,
21833                    }),
21834                    data: None,
21835                },
21836                lsp::CodeLens {
21837                    range: lsp::Range::default(),
21838                    command: Some(lsp::Command {
21839                        title: "Command not in capabilities".to_owned(),
21840                        command: "not in capabilities".to_owned(),
21841                        arguments: None,
21842                    }),
21843                    data: None,
21844                },
21845                lsp::CodeLens {
21846                    range: lsp::Range {
21847                        start: lsp::Position {
21848                            line: 1,
21849                            character: 1,
21850                        },
21851                        end: lsp::Position {
21852                            line: 1,
21853                            character: 1,
21854                        },
21855                    },
21856                    command: Some(lsp::Command {
21857                        title: "Command not in range".to_owned(),
21858                        command: "_the/command".to_owned(),
21859                        arguments: None,
21860                    }),
21861                    data: None,
21862                },
21863            ]))
21864        })
21865        .next()
21866        .await;
21867
21868    let actions = actions.await.unwrap();
21869    assert_eq!(
21870        actions.len(),
21871        1,
21872        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
21873    );
21874    let action = actions[0].clone();
21875    let apply = project.update(cx, |project, cx| {
21876        project.apply_code_action(buffer.clone(), action, true, cx)
21877    });
21878
21879    // Resolving the code action does not populate its edits. In absence of
21880    // edits, we must execute the given command.
21881    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
21882        |mut lens, _| async move {
21883            let lens_command = lens.command.as_mut().expect("should have a command");
21884            assert_eq!(lens_command.title, "Code lens command");
21885            lens_command.arguments = Some(vec![json!("the-argument")]);
21886            Ok(lens)
21887        },
21888    );
21889
21890    // While executing the command, the language server sends the editor
21891    // a `workspaceEdit` request.
21892    fake_server
21893        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
21894            let fake = fake_server.clone();
21895            move |params, _| {
21896                assert_eq!(params.command, "_the/command");
21897                let fake = fake.clone();
21898                async move {
21899                    fake.server
21900                        .request::<lsp::request::ApplyWorkspaceEdit>(
21901                            lsp::ApplyWorkspaceEditParams {
21902                                label: None,
21903                                edit: lsp::WorkspaceEdit {
21904                                    changes: Some(
21905                                        [(
21906                                            lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
21907                                            vec![lsp::TextEdit {
21908                                                range: lsp::Range::new(
21909                                                    lsp::Position::new(0, 0),
21910                                                    lsp::Position::new(0, 0),
21911                                                ),
21912                                                new_text: "X".into(),
21913                                            }],
21914                                        )]
21915                                        .into_iter()
21916                                        .collect(),
21917                                    ),
21918                                    ..lsp::WorkspaceEdit::default()
21919                                },
21920                            },
21921                        )
21922                        .await
21923                        .into_response()
21924                        .unwrap();
21925                    Ok(Some(json!(null)))
21926                }
21927            }
21928        })
21929        .next()
21930        .await;
21931
21932    // Applying the code lens command returns a project transaction containing the edits
21933    // sent by the language server in its `workspaceEdit` request.
21934    let transaction = apply.await.unwrap();
21935    assert!(transaction.0.contains_key(&buffer));
21936    buffer.update(cx, |buffer, cx| {
21937        assert_eq!(buffer.text(), "Xa");
21938        buffer.undo(cx);
21939        assert_eq!(buffer.text(), "a");
21940    });
21941
21942    let actions_after_edits = cx
21943        .update_window(*workspace, |_, window, cx| {
21944            project.code_actions(&buffer, anchor..anchor, window, cx)
21945        })
21946        .unwrap()
21947        .await
21948        .unwrap();
21949    assert_eq!(
21950        actions, actions_after_edits,
21951        "For the same selection, same code lens actions should be returned"
21952    );
21953
21954    let _responses =
21955        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
21956            panic!("No more code lens requests are expected");
21957        });
21958    editor.update_in(cx, |editor, window, cx| {
21959        editor.select_all(&SelectAll, window, cx);
21960    });
21961    cx.executor().run_until_parked();
21962    let new_actions = cx
21963        .update_window(*workspace, |_, window, cx| {
21964            project.code_actions(&buffer, anchor..anchor, window, cx)
21965        })
21966        .unwrap()
21967        .await
21968        .unwrap();
21969    assert_eq!(
21970        actions, new_actions,
21971        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
21972    );
21973}
21974
21975#[gpui::test]
21976async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
21977    init_test(cx, |_| {});
21978
21979    let fs = FakeFs::new(cx.executor());
21980    let main_text = r#"fn main() {
21981println!("1");
21982println!("2");
21983println!("3");
21984println!("4");
21985println!("5");
21986}"#;
21987    let lib_text = "mod foo {}";
21988    fs.insert_tree(
21989        path!("/a"),
21990        json!({
21991            "lib.rs": lib_text,
21992            "main.rs": main_text,
21993        }),
21994    )
21995    .await;
21996
21997    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21998    let (workspace, cx) =
21999        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22000    let worktree_id = workspace.update(cx, |workspace, cx| {
22001        workspace.project().update(cx, |project, cx| {
22002            project.worktrees(cx).next().unwrap().read(cx).id()
22003        })
22004    });
22005
22006    let expected_ranges = vec![
22007        Point::new(0, 0)..Point::new(0, 0),
22008        Point::new(1, 0)..Point::new(1, 1),
22009        Point::new(2, 0)..Point::new(2, 2),
22010        Point::new(3, 0)..Point::new(3, 3),
22011    ];
22012
22013    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
22014    let editor_1 = workspace
22015        .update_in(cx, |workspace, window, cx| {
22016            workspace.open_path(
22017                (worktree_id, "main.rs"),
22018                Some(pane_1.downgrade()),
22019                true,
22020                window,
22021                cx,
22022            )
22023        })
22024        .unwrap()
22025        .await
22026        .downcast::<Editor>()
22027        .unwrap();
22028    pane_1.update(cx, |pane, cx| {
22029        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22030        open_editor.update(cx, |editor, cx| {
22031            assert_eq!(
22032                editor.display_text(cx),
22033                main_text,
22034                "Original main.rs text on initial open",
22035            );
22036            assert_eq!(
22037                editor
22038                    .selections
22039                    .all::<Point>(cx)
22040                    .into_iter()
22041                    .map(|s| s.range())
22042                    .collect::<Vec<_>>(),
22043                vec![Point::zero()..Point::zero()],
22044                "Default selections on initial open",
22045            );
22046        })
22047    });
22048    editor_1.update_in(cx, |editor, window, cx| {
22049        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22050            s.select_ranges(expected_ranges.clone());
22051        });
22052    });
22053
22054    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
22055        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
22056    });
22057    let editor_2 = workspace
22058        .update_in(cx, |workspace, window, cx| {
22059            workspace.open_path(
22060                (worktree_id, "main.rs"),
22061                Some(pane_2.downgrade()),
22062                true,
22063                window,
22064                cx,
22065            )
22066        })
22067        .unwrap()
22068        .await
22069        .downcast::<Editor>()
22070        .unwrap();
22071    pane_2.update(cx, |pane, cx| {
22072        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22073        open_editor.update(cx, |editor, cx| {
22074            assert_eq!(
22075                editor.display_text(cx),
22076                main_text,
22077                "Original main.rs text on initial open in another panel",
22078            );
22079            assert_eq!(
22080                editor
22081                    .selections
22082                    .all::<Point>(cx)
22083                    .into_iter()
22084                    .map(|s| s.range())
22085                    .collect::<Vec<_>>(),
22086                vec![Point::zero()..Point::zero()],
22087                "Default selections on initial open in another panel",
22088            );
22089        })
22090    });
22091
22092    editor_2.update_in(cx, |editor, window, cx| {
22093        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
22094    });
22095
22096    let _other_editor_1 = workspace
22097        .update_in(cx, |workspace, window, cx| {
22098            workspace.open_path(
22099                (worktree_id, "lib.rs"),
22100                Some(pane_1.downgrade()),
22101                true,
22102                window,
22103                cx,
22104            )
22105        })
22106        .unwrap()
22107        .await
22108        .downcast::<Editor>()
22109        .unwrap();
22110    pane_1
22111        .update_in(cx, |pane, window, cx| {
22112            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
22113        })
22114        .await
22115        .unwrap();
22116    drop(editor_1);
22117    pane_1.update(cx, |pane, cx| {
22118        pane.active_item()
22119            .unwrap()
22120            .downcast::<Editor>()
22121            .unwrap()
22122            .update(cx, |editor, cx| {
22123                assert_eq!(
22124                    editor.display_text(cx),
22125                    lib_text,
22126                    "Other file should be open and active",
22127                );
22128            });
22129        assert_eq!(pane.items().count(), 1, "No other editors should be open");
22130    });
22131
22132    let _other_editor_2 = workspace
22133        .update_in(cx, |workspace, window, cx| {
22134            workspace.open_path(
22135                (worktree_id, "lib.rs"),
22136                Some(pane_2.downgrade()),
22137                true,
22138                window,
22139                cx,
22140            )
22141        })
22142        .unwrap()
22143        .await
22144        .downcast::<Editor>()
22145        .unwrap();
22146    pane_2
22147        .update_in(cx, |pane, window, cx| {
22148            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
22149        })
22150        .await
22151        .unwrap();
22152    drop(editor_2);
22153    pane_2.update(cx, |pane, cx| {
22154        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22155        open_editor.update(cx, |editor, cx| {
22156            assert_eq!(
22157                editor.display_text(cx),
22158                lib_text,
22159                "Other file should be open and active in another panel too",
22160            );
22161        });
22162        assert_eq!(
22163            pane.items().count(),
22164            1,
22165            "No other editors should be open in another pane",
22166        );
22167    });
22168
22169    let _editor_1_reopened = workspace
22170        .update_in(cx, |workspace, window, cx| {
22171            workspace.open_path(
22172                (worktree_id, "main.rs"),
22173                Some(pane_1.downgrade()),
22174                true,
22175                window,
22176                cx,
22177            )
22178        })
22179        .unwrap()
22180        .await
22181        .downcast::<Editor>()
22182        .unwrap();
22183    let _editor_2_reopened = workspace
22184        .update_in(cx, |workspace, window, cx| {
22185            workspace.open_path(
22186                (worktree_id, "main.rs"),
22187                Some(pane_2.downgrade()),
22188                true,
22189                window,
22190                cx,
22191            )
22192        })
22193        .unwrap()
22194        .await
22195        .downcast::<Editor>()
22196        .unwrap();
22197    pane_1.update(cx, |pane, cx| {
22198        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22199        open_editor.update(cx, |editor, cx| {
22200            assert_eq!(
22201                editor.display_text(cx),
22202                main_text,
22203                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
22204            );
22205            assert_eq!(
22206                editor
22207                    .selections
22208                    .all::<Point>(cx)
22209                    .into_iter()
22210                    .map(|s| s.range())
22211                    .collect::<Vec<_>>(),
22212                expected_ranges,
22213                "Previous editor in the 1st panel had selections and should get them restored on reopen",
22214            );
22215        })
22216    });
22217    pane_2.update(cx, |pane, cx| {
22218        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22219        open_editor.update(cx, |editor, cx| {
22220            assert_eq!(
22221                editor.display_text(cx),
22222                r#"fn main() {
22223⋯rintln!("1");
22224⋯intln!("2");
22225⋯ntln!("3");
22226println!("4");
22227println!("5");
22228}"#,
22229                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
22230            );
22231            assert_eq!(
22232                editor
22233                    .selections
22234                    .all::<Point>(cx)
22235                    .into_iter()
22236                    .map(|s| s.range())
22237                    .collect::<Vec<_>>(),
22238                vec![Point::zero()..Point::zero()],
22239                "Previous editor in the 2nd pane had no selections changed hence should restore none",
22240            );
22241        })
22242    });
22243}
22244
22245#[gpui::test]
22246async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
22247    init_test(cx, |_| {});
22248
22249    let fs = FakeFs::new(cx.executor());
22250    let main_text = r#"fn main() {
22251println!("1");
22252println!("2");
22253println!("3");
22254println!("4");
22255println!("5");
22256}"#;
22257    let lib_text = "mod foo {}";
22258    fs.insert_tree(
22259        path!("/a"),
22260        json!({
22261            "lib.rs": lib_text,
22262            "main.rs": main_text,
22263        }),
22264    )
22265    .await;
22266
22267    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22268    let (workspace, cx) =
22269        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22270    let worktree_id = workspace.update(cx, |workspace, cx| {
22271        workspace.project().update(cx, |project, cx| {
22272            project.worktrees(cx).next().unwrap().read(cx).id()
22273        })
22274    });
22275
22276    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
22277    let editor = workspace
22278        .update_in(cx, |workspace, window, cx| {
22279            workspace.open_path(
22280                (worktree_id, "main.rs"),
22281                Some(pane.downgrade()),
22282                true,
22283                window,
22284                cx,
22285            )
22286        })
22287        .unwrap()
22288        .await
22289        .downcast::<Editor>()
22290        .unwrap();
22291    pane.update(cx, |pane, cx| {
22292        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22293        open_editor.update(cx, |editor, cx| {
22294            assert_eq!(
22295                editor.display_text(cx),
22296                main_text,
22297                "Original main.rs text on initial open",
22298            );
22299        })
22300    });
22301    editor.update_in(cx, |editor, window, cx| {
22302        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
22303    });
22304
22305    cx.update_global(|store: &mut SettingsStore, cx| {
22306        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
22307            s.restore_on_file_reopen = Some(false);
22308        });
22309    });
22310    editor.update_in(cx, |editor, window, cx| {
22311        editor.fold_ranges(
22312            vec![
22313                Point::new(1, 0)..Point::new(1, 1),
22314                Point::new(2, 0)..Point::new(2, 2),
22315                Point::new(3, 0)..Point::new(3, 3),
22316            ],
22317            false,
22318            window,
22319            cx,
22320        );
22321    });
22322    pane.update_in(cx, |pane, window, cx| {
22323        pane.close_all_items(&CloseAllItems::default(), window, cx)
22324    })
22325    .await
22326    .unwrap();
22327    pane.update(cx, |pane, _| {
22328        assert!(pane.active_item().is_none());
22329    });
22330    cx.update_global(|store: &mut SettingsStore, cx| {
22331        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
22332            s.restore_on_file_reopen = Some(true);
22333        });
22334    });
22335
22336    let _editor_reopened = workspace
22337        .update_in(cx, |workspace, window, cx| {
22338            workspace.open_path(
22339                (worktree_id, "main.rs"),
22340                Some(pane.downgrade()),
22341                true,
22342                window,
22343                cx,
22344            )
22345        })
22346        .unwrap()
22347        .await
22348        .downcast::<Editor>()
22349        .unwrap();
22350    pane.update(cx, |pane, cx| {
22351        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22352        open_editor.update(cx, |editor, cx| {
22353            assert_eq!(
22354                editor.display_text(cx),
22355                main_text,
22356                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
22357            );
22358        })
22359    });
22360}
22361
22362#[gpui::test]
22363async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
22364    struct EmptyModalView {
22365        focus_handle: gpui::FocusHandle,
22366    }
22367    impl EventEmitter<DismissEvent> for EmptyModalView {}
22368    impl Render for EmptyModalView {
22369        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
22370            div()
22371        }
22372    }
22373    impl Focusable for EmptyModalView {
22374        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
22375            self.focus_handle.clone()
22376        }
22377    }
22378    impl workspace::ModalView for EmptyModalView {}
22379    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
22380        EmptyModalView {
22381            focus_handle: cx.focus_handle(),
22382        }
22383    }
22384
22385    init_test(cx, |_| {});
22386
22387    let fs = FakeFs::new(cx.executor());
22388    let project = Project::test(fs, [], cx).await;
22389    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22390    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
22391    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22392    let editor = cx.new_window_entity(|window, cx| {
22393        Editor::new(
22394            EditorMode::full(),
22395            buffer,
22396            Some(project.clone()),
22397            window,
22398            cx,
22399        )
22400    });
22401    workspace
22402        .update(cx, |workspace, window, cx| {
22403            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
22404        })
22405        .unwrap();
22406    editor.update_in(cx, |editor, window, cx| {
22407        editor.open_context_menu(&OpenContextMenu, window, cx);
22408        assert!(editor.mouse_context_menu.is_some());
22409    });
22410    workspace
22411        .update(cx, |workspace, window, cx| {
22412            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
22413        })
22414        .unwrap();
22415    cx.read(|cx| {
22416        assert!(editor.read(cx).mouse_context_menu.is_none());
22417    });
22418}
22419
22420#[gpui::test]
22421async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
22422    init_test(cx, |_| {});
22423
22424    let fs = FakeFs::new(cx.executor());
22425    fs.insert_file(path!("/file.html"), Default::default())
22426        .await;
22427
22428    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
22429
22430    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22431    let html_language = Arc::new(Language::new(
22432        LanguageConfig {
22433            name: "HTML".into(),
22434            matcher: LanguageMatcher {
22435                path_suffixes: vec!["html".to_string()],
22436                ..LanguageMatcher::default()
22437            },
22438            brackets: BracketPairConfig {
22439                pairs: vec![BracketPair {
22440                    start: "<".into(),
22441                    end: ">".into(),
22442                    close: true,
22443                    ..Default::default()
22444                }],
22445                ..Default::default()
22446            },
22447            ..Default::default()
22448        },
22449        Some(tree_sitter_html::LANGUAGE.into()),
22450    ));
22451    language_registry.add(html_language);
22452    let mut fake_servers = language_registry.register_fake_lsp(
22453        "HTML",
22454        FakeLspAdapter {
22455            capabilities: lsp::ServerCapabilities {
22456                completion_provider: Some(lsp::CompletionOptions {
22457                    resolve_provider: Some(true),
22458                    ..Default::default()
22459                }),
22460                ..Default::default()
22461            },
22462            ..Default::default()
22463        },
22464    );
22465
22466    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22467    let cx = &mut VisualTestContext::from_window(*workspace, cx);
22468
22469    let worktree_id = workspace
22470        .update(cx, |workspace, _window, cx| {
22471            workspace.project().update(cx, |project, cx| {
22472                project.worktrees(cx).next().unwrap().read(cx).id()
22473            })
22474        })
22475        .unwrap();
22476    project
22477        .update(cx, |project, cx| {
22478            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
22479        })
22480        .await
22481        .unwrap();
22482    let editor = workspace
22483        .update(cx, |workspace, window, cx| {
22484            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
22485        })
22486        .unwrap()
22487        .await
22488        .unwrap()
22489        .downcast::<Editor>()
22490        .unwrap();
22491
22492    let fake_server = fake_servers.next().await.unwrap();
22493    editor.update_in(cx, |editor, window, cx| {
22494        editor.set_text("<ad></ad>", window, cx);
22495        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22496            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
22497        });
22498        let Some((buffer, _)) = editor
22499            .buffer
22500            .read(cx)
22501            .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
22502        else {
22503            panic!("Failed to get buffer for selection position");
22504        };
22505        let buffer = buffer.read(cx);
22506        let buffer_id = buffer.remote_id();
22507        let opening_range =
22508            buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
22509        let closing_range =
22510            buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
22511        let mut linked_ranges = HashMap::default();
22512        linked_ranges.insert(
22513            buffer_id,
22514            vec![(opening_range.clone(), vec![closing_range.clone()])],
22515        );
22516        editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
22517    });
22518    let mut completion_handle =
22519        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
22520            Ok(Some(lsp::CompletionResponse::Array(vec![
22521                lsp::CompletionItem {
22522                    label: "head".to_string(),
22523                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22524                        lsp::InsertReplaceEdit {
22525                            new_text: "head".to_string(),
22526                            insert: lsp::Range::new(
22527                                lsp::Position::new(0, 1),
22528                                lsp::Position::new(0, 3),
22529                            ),
22530                            replace: lsp::Range::new(
22531                                lsp::Position::new(0, 1),
22532                                lsp::Position::new(0, 3),
22533                            ),
22534                        },
22535                    )),
22536                    ..Default::default()
22537                },
22538            ])))
22539        });
22540    editor.update_in(cx, |editor, window, cx| {
22541        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
22542    });
22543    cx.run_until_parked();
22544    completion_handle.next().await.unwrap();
22545    editor.update(cx, |editor, _| {
22546        assert!(
22547            editor.context_menu_visible(),
22548            "Completion menu should be visible"
22549        );
22550    });
22551    editor.update_in(cx, |editor, window, cx| {
22552        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
22553    });
22554    cx.executor().run_until_parked();
22555    editor.update(cx, |editor, cx| {
22556        assert_eq!(editor.text(cx), "<head></head>");
22557    });
22558}
22559
22560#[gpui::test]
22561async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
22562    init_test(cx, |_| {});
22563
22564    let fs = FakeFs::new(cx.executor());
22565    fs.insert_tree(
22566        path!("/root"),
22567        json!({
22568            "a": {
22569                "main.rs": "fn main() {}",
22570            },
22571            "foo": {
22572                "bar": {
22573                    "external_file.rs": "pub mod external {}",
22574                }
22575            }
22576        }),
22577    )
22578    .await;
22579
22580    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
22581    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22582    language_registry.add(rust_lang());
22583    let _fake_servers = language_registry.register_fake_lsp(
22584        "Rust",
22585        FakeLspAdapter {
22586            ..FakeLspAdapter::default()
22587        },
22588    );
22589    let (workspace, cx) =
22590        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22591    let worktree_id = workspace.update(cx, |workspace, cx| {
22592        workspace.project().update(cx, |project, cx| {
22593            project.worktrees(cx).next().unwrap().read(cx).id()
22594        })
22595    });
22596
22597    let assert_language_servers_count =
22598        |expected: usize, context: &str, cx: &mut VisualTestContext| {
22599            project.update(cx, |project, cx| {
22600                let current = project
22601                    .lsp_store()
22602                    .read(cx)
22603                    .as_local()
22604                    .unwrap()
22605                    .language_servers
22606                    .len();
22607                assert_eq!(expected, current, "{context}");
22608            });
22609        };
22610
22611    assert_language_servers_count(
22612        0,
22613        "No servers should be running before any file is open",
22614        cx,
22615    );
22616    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
22617    let main_editor = workspace
22618        .update_in(cx, |workspace, window, cx| {
22619            workspace.open_path(
22620                (worktree_id, "main.rs"),
22621                Some(pane.downgrade()),
22622                true,
22623                window,
22624                cx,
22625            )
22626        })
22627        .unwrap()
22628        .await
22629        .downcast::<Editor>()
22630        .unwrap();
22631    pane.update(cx, |pane, cx| {
22632        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22633        open_editor.update(cx, |editor, cx| {
22634            assert_eq!(
22635                editor.display_text(cx),
22636                "fn main() {}",
22637                "Original main.rs text on initial open",
22638            );
22639        });
22640        assert_eq!(open_editor, main_editor);
22641    });
22642    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
22643
22644    let external_editor = workspace
22645        .update_in(cx, |workspace, window, cx| {
22646            workspace.open_abs_path(
22647                PathBuf::from("/root/foo/bar/external_file.rs"),
22648                OpenOptions::default(),
22649                window,
22650                cx,
22651            )
22652        })
22653        .await
22654        .expect("opening external file")
22655        .downcast::<Editor>()
22656        .expect("downcasted external file's open element to editor");
22657    pane.update(cx, |pane, cx| {
22658        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22659        open_editor.update(cx, |editor, cx| {
22660            assert_eq!(
22661                editor.display_text(cx),
22662                "pub mod external {}",
22663                "External file is open now",
22664            );
22665        });
22666        assert_eq!(open_editor, external_editor);
22667    });
22668    assert_language_servers_count(
22669        1,
22670        "Second, external, *.rs file should join the existing server",
22671        cx,
22672    );
22673
22674    pane.update_in(cx, |pane, window, cx| {
22675        pane.close_active_item(&CloseActiveItem::default(), window, cx)
22676    })
22677    .await
22678    .unwrap();
22679    pane.update_in(cx, |pane, window, cx| {
22680        pane.navigate_backward(window, cx);
22681    });
22682    cx.run_until_parked();
22683    pane.update(cx, |pane, cx| {
22684        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22685        open_editor.update(cx, |editor, cx| {
22686            assert_eq!(
22687                editor.display_text(cx),
22688                "pub mod external {}",
22689                "External file is open now",
22690            );
22691        });
22692    });
22693    assert_language_servers_count(
22694        1,
22695        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
22696        cx,
22697    );
22698
22699    cx.update(|_, cx| {
22700        workspace::reload(cx);
22701    });
22702    assert_language_servers_count(
22703        1,
22704        "After reloading the worktree with local and external files opened, only one project should be started",
22705        cx,
22706    );
22707}
22708
22709#[gpui::test]
22710async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
22711    init_test(cx, |_| {});
22712
22713    let mut cx = EditorTestContext::new(cx).await;
22714    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22715    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22716
22717    // test cursor move to start of each line on tab
22718    // for `if`, `elif`, `else`, `while`, `with` and `for`
22719    cx.set_state(indoc! {"
22720        def main():
22721        ˇ    for item in items:
22722        ˇ        while item.active:
22723        ˇ            if item.value > 10:
22724        ˇ                continue
22725        ˇ            elif item.value < 0:
22726        ˇ                break
22727        ˇ            else:
22728        ˇ                with item.context() as ctx:
22729        ˇ                    yield count
22730        ˇ        else:
22731        ˇ            log('while else')
22732        ˇ    else:
22733        ˇ        log('for else')
22734    "});
22735    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22736    cx.assert_editor_state(indoc! {"
22737        def main():
22738            ˇfor item in items:
22739                ˇwhile item.active:
22740                    ˇif item.value > 10:
22741                        ˇcontinue
22742                    ˇelif item.value < 0:
22743                        ˇbreak
22744                    ˇelse:
22745                        ˇwith item.context() as ctx:
22746                            ˇyield count
22747                ˇelse:
22748                    ˇlog('while else')
22749            ˇelse:
22750                ˇlog('for else')
22751    "});
22752    // test relative indent is preserved when tab
22753    // for `if`, `elif`, `else`, `while`, `with` and `for`
22754    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22755    cx.assert_editor_state(indoc! {"
22756        def main():
22757                ˇfor item in items:
22758                    ˇwhile item.active:
22759                        ˇif item.value > 10:
22760                            ˇcontinue
22761                        ˇelif item.value < 0:
22762                            ˇbreak
22763                        ˇelse:
22764                            ˇwith item.context() as ctx:
22765                                ˇyield count
22766                    ˇelse:
22767                        ˇlog('while else')
22768                ˇelse:
22769                    ˇlog('for else')
22770    "});
22771
22772    // test cursor move to start of each line on tab
22773    // for `try`, `except`, `else`, `finally`, `match` and `def`
22774    cx.set_state(indoc! {"
22775        def main():
22776        ˇ    try:
22777        ˇ        fetch()
22778        ˇ    except ValueError:
22779        ˇ        handle_error()
22780        ˇ    else:
22781        ˇ        match value:
22782        ˇ            case _:
22783        ˇ    finally:
22784        ˇ        def status():
22785        ˇ            return 0
22786    "});
22787    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22788    cx.assert_editor_state(indoc! {"
22789        def main():
22790            ˇtry:
22791                ˇfetch()
22792            ˇexcept ValueError:
22793                ˇhandle_error()
22794            ˇelse:
22795                ˇmatch value:
22796                    ˇcase _:
22797            ˇfinally:
22798                ˇdef status():
22799                    ˇreturn 0
22800    "});
22801    // test relative indent is preserved when tab
22802    // for `try`, `except`, `else`, `finally`, `match` and `def`
22803    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22804    cx.assert_editor_state(indoc! {"
22805        def main():
22806                ˇtry:
22807                    ˇfetch()
22808                ˇexcept ValueError:
22809                    ˇhandle_error()
22810                ˇelse:
22811                    ˇmatch value:
22812                        ˇcase _:
22813                ˇfinally:
22814                    ˇdef status():
22815                        ˇreturn 0
22816    "});
22817}
22818
22819#[gpui::test]
22820async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
22821    init_test(cx, |_| {});
22822
22823    let mut cx = EditorTestContext::new(cx).await;
22824    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22825    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22826
22827    // test `else` auto outdents when typed inside `if` block
22828    cx.set_state(indoc! {"
22829        def main():
22830            if i == 2:
22831                return
22832                ˇ
22833    "});
22834    cx.update_editor(|editor, window, cx| {
22835        editor.handle_input("else:", window, cx);
22836    });
22837    cx.assert_editor_state(indoc! {"
22838        def main():
22839            if i == 2:
22840                return
22841            else:ˇ
22842    "});
22843
22844    // test `except` auto outdents when typed inside `try` block
22845    cx.set_state(indoc! {"
22846        def main():
22847            try:
22848                i = 2
22849                ˇ
22850    "});
22851    cx.update_editor(|editor, window, cx| {
22852        editor.handle_input("except:", window, cx);
22853    });
22854    cx.assert_editor_state(indoc! {"
22855        def main():
22856            try:
22857                i = 2
22858            except:ˇ
22859    "});
22860
22861    // test `else` auto outdents when typed inside `except` block
22862    cx.set_state(indoc! {"
22863        def main():
22864            try:
22865                i = 2
22866            except:
22867                j = 2
22868                ˇ
22869    "});
22870    cx.update_editor(|editor, window, cx| {
22871        editor.handle_input("else:", window, cx);
22872    });
22873    cx.assert_editor_state(indoc! {"
22874        def main():
22875            try:
22876                i = 2
22877            except:
22878                j = 2
22879            else:ˇ
22880    "});
22881
22882    // test `finally` auto outdents when typed inside `else` block
22883    cx.set_state(indoc! {"
22884        def main():
22885            try:
22886                i = 2
22887            except:
22888                j = 2
22889            else:
22890                k = 2
22891                ˇ
22892    "});
22893    cx.update_editor(|editor, window, cx| {
22894        editor.handle_input("finally:", window, cx);
22895    });
22896    cx.assert_editor_state(indoc! {"
22897        def main():
22898            try:
22899                i = 2
22900            except:
22901                j = 2
22902            else:
22903                k = 2
22904            finally:ˇ
22905    "});
22906
22907    // test `else` does not outdents when typed inside `except` block right after for block
22908    cx.set_state(indoc! {"
22909        def main():
22910            try:
22911                i = 2
22912            except:
22913                for i in range(n):
22914                    pass
22915                ˇ
22916    "});
22917    cx.update_editor(|editor, window, cx| {
22918        editor.handle_input("else:", window, cx);
22919    });
22920    cx.assert_editor_state(indoc! {"
22921        def main():
22922            try:
22923                i = 2
22924            except:
22925                for i in range(n):
22926                    pass
22927                else:ˇ
22928    "});
22929
22930    // test `finally` auto outdents when typed inside `else` block right after for block
22931    cx.set_state(indoc! {"
22932        def main():
22933            try:
22934                i = 2
22935            except:
22936                j = 2
22937            else:
22938                for i in range(n):
22939                    pass
22940                ˇ
22941    "});
22942    cx.update_editor(|editor, window, cx| {
22943        editor.handle_input("finally:", window, cx);
22944    });
22945    cx.assert_editor_state(indoc! {"
22946        def main():
22947            try:
22948                i = 2
22949            except:
22950                j = 2
22951            else:
22952                for i in range(n):
22953                    pass
22954            finally:ˇ
22955    "});
22956
22957    // test `except` outdents to inner "try" block
22958    cx.set_state(indoc! {"
22959        def main():
22960            try:
22961                i = 2
22962                if i == 2:
22963                    try:
22964                        i = 3
22965                        ˇ
22966    "});
22967    cx.update_editor(|editor, window, cx| {
22968        editor.handle_input("except:", window, cx);
22969    });
22970    cx.assert_editor_state(indoc! {"
22971        def main():
22972            try:
22973                i = 2
22974                if i == 2:
22975                    try:
22976                        i = 3
22977                    except:ˇ
22978    "});
22979
22980    // test `except` outdents to outer "try" block
22981    cx.set_state(indoc! {"
22982        def main():
22983            try:
22984                i = 2
22985                if i == 2:
22986                    try:
22987                        i = 3
22988                ˇ
22989    "});
22990    cx.update_editor(|editor, window, cx| {
22991        editor.handle_input("except:", window, cx);
22992    });
22993    cx.assert_editor_state(indoc! {"
22994        def main():
22995            try:
22996                i = 2
22997                if i == 2:
22998                    try:
22999                        i = 3
23000            except:ˇ
23001    "});
23002
23003    // test `else` stays at correct indent when typed after `for` block
23004    cx.set_state(indoc! {"
23005        def main():
23006            for i in range(10):
23007                if i == 3:
23008                    break
23009            ˇ
23010    "});
23011    cx.update_editor(|editor, window, cx| {
23012        editor.handle_input("else:", window, cx);
23013    });
23014    cx.assert_editor_state(indoc! {"
23015        def main():
23016            for i in range(10):
23017                if i == 3:
23018                    break
23019            else:ˇ
23020    "});
23021
23022    // test does not outdent on typing after line with square brackets
23023    cx.set_state(indoc! {"
23024        def f() -> list[str]:
23025            ˇ
23026    "});
23027    cx.update_editor(|editor, window, cx| {
23028        editor.handle_input("a", window, cx);
23029    });
23030    cx.assert_editor_state(indoc! {"
23031        def f() -> list[str]:
2303223033    "});
23034
23035    // test does not outdent on typing : after case keyword
23036    cx.set_state(indoc! {"
23037        match 1:
23038            caseˇ
23039    "});
23040    cx.update_editor(|editor, window, cx| {
23041        editor.handle_input(":", window, cx);
23042    });
23043    cx.assert_editor_state(indoc! {"
23044        match 1:
23045            case:ˇ
23046    "});
23047}
23048
23049#[gpui::test]
23050async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
23051    init_test(cx, |_| {});
23052    update_test_language_settings(cx, |settings| {
23053        settings.defaults.extend_comment_on_newline = Some(false);
23054    });
23055    let mut cx = EditorTestContext::new(cx).await;
23056    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
23057    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23058
23059    // test correct indent after newline on comment
23060    cx.set_state(indoc! {"
23061        # COMMENT:ˇ
23062    "});
23063    cx.update_editor(|editor, window, cx| {
23064        editor.newline(&Newline, window, cx);
23065    });
23066    cx.assert_editor_state(indoc! {"
23067        # COMMENT:
23068        ˇ
23069    "});
23070
23071    // test correct indent after newline in brackets
23072    cx.set_state(indoc! {"
23073        {ˇ}
23074    "});
23075    cx.update_editor(|editor, window, cx| {
23076        editor.newline(&Newline, window, cx);
23077    });
23078    cx.run_until_parked();
23079    cx.assert_editor_state(indoc! {"
23080        {
23081            ˇ
23082        }
23083    "});
23084
23085    cx.set_state(indoc! {"
23086        (ˇ)
23087    "});
23088    cx.update_editor(|editor, window, cx| {
23089        editor.newline(&Newline, window, cx);
23090    });
23091    cx.run_until_parked();
23092    cx.assert_editor_state(indoc! {"
23093        (
23094            ˇ
23095        )
23096    "});
23097
23098    // do not indent after empty lists or dictionaries
23099    cx.set_state(indoc! {"
23100        a = []ˇ
23101    "});
23102    cx.update_editor(|editor, window, cx| {
23103        editor.newline(&Newline, window, cx);
23104    });
23105    cx.run_until_parked();
23106    cx.assert_editor_state(indoc! {"
23107        a = []
23108        ˇ
23109    "});
23110}
23111
23112#[gpui::test]
23113async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
23114    init_test(cx, |_| {});
23115
23116    let mut cx = EditorTestContext::new(cx).await;
23117    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
23118    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23119
23120    // test cursor move to start of each line on tab
23121    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
23122    cx.set_state(indoc! {"
23123        function main() {
23124        ˇ    for item in $items; do
23125        ˇ        while [ -n \"$item\" ]; do
23126        ˇ            if [ \"$value\" -gt 10 ]; then
23127        ˇ                continue
23128        ˇ            elif [ \"$value\" -lt 0 ]; then
23129        ˇ                break
23130        ˇ            else
23131        ˇ                echo \"$item\"
23132        ˇ            fi
23133        ˇ        done
23134        ˇ    done
23135        ˇ}
23136    "});
23137    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
23138    cx.assert_editor_state(indoc! {"
23139        function main() {
23140            ˇfor item in $items; do
23141                ˇwhile [ -n \"$item\" ]; do
23142                    ˇif [ \"$value\" -gt 10 ]; then
23143                        ˇcontinue
23144                    ˇelif [ \"$value\" -lt 0 ]; then
23145                        ˇbreak
23146                    ˇelse
23147                        ˇecho \"$item\"
23148                    ˇfi
23149                ˇdone
23150            ˇdone
23151        ˇ}
23152    "});
23153    // test relative indent is preserved when tab
23154    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
23155    cx.assert_editor_state(indoc! {"
23156        function main() {
23157                ˇfor item in $items; do
23158                    ˇwhile [ -n \"$item\" ]; do
23159                        ˇif [ \"$value\" -gt 10 ]; then
23160                            ˇcontinue
23161                        ˇelif [ \"$value\" -lt 0 ]; then
23162                            ˇbreak
23163                        ˇelse
23164                            ˇecho \"$item\"
23165                        ˇfi
23166                    ˇdone
23167                ˇdone
23168            ˇ}
23169    "});
23170
23171    // test cursor move to start of each line on tab
23172    // for `case` statement with patterns
23173    cx.set_state(indoc! {"
23174        function handle() {
23175        ˇ    case \"$1\" in
23176        ˇ        start)
23177        ˇ            echo \"a\"
23178        ˇ            ;;
23179        ˇ        stop)
23180        ˇ            echo \"b\"
23181        ˇ            ;;
23182        ˇ        *)
23183        ˇ            echo \"c\"
23184        ˇ            ;;
23185        ˇ    esac
23186        ˇ}
23187    "});
23188    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
23189    cx.assert_editor_state(indoc! {"
23190        function handle() {
23191            ˇcase \"$1\" in
23192                ˇstart)
23193                    ˇecho \"a\"
23194                    ˇ;;
23195                ˇstop)
23196                    ˇecho \"b\"
23197                    ˇ;;
23198                ˇ*)
23199                    ˇecho \"c\"
23200                    ˇ;;
23201            ˇesac
23202        ˇ}
23203    "});
23204}
23205
23206#[gpui::test]
23207async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
23208    init_test(cx, |_| {});
23209
23210    let mut cx = EditorTestContext::new(cx).await;
23211    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
23212    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23213
23214    // test indents on comment insert
23215    cx.set_state(indoc! {"
23216        function main() {
23217        ˇ    for item in $items; do
23218        ˇ        while [ -n \"$item\" ]; do
23219        ˇ            if [ \"$value\" -gt 10 ]; then
23220        ˇ                continue
23221        ˇ            elif [ \"$value\" -lt 0 ]; then
23222        ˇ                break
23223        ˇ            else
23224        ˇ                echo \"$item\"
23225        ˇ            fi
23226        ˇ        done
23227        ˇ    done
23228        ˇ}
23229    "});
23230    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
23231    cx.assert_editor_state(indoc! {"
23232        function main() {
23233        #ˇ    for item in $items; do
23234        #ˇ        while [ -n \"$item\" ]; do
23235        #ˇ            if [ \"$value\" -gt 10 ]; then
23236        #ˇ                continue
23237        #ˇ            elif [ \"$value\" -lt 0 ]; then
23238        #ˇ                break
23239        #ˇ            else
23240        #ˇ                echo \"$item\"
23241        #ˇ            fi
23242        #ˇ        done
23243        #ˇ    done
23244        #ˇ}
23245    "});
23246}
23247
23248#[gpui::test]
23249async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
23250    init_test(cx, |_| {});
23251
23252    let mut cx = EditorTestContext::new(cx).await;
23253    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
23254    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23255
23256    // test `else` auto outdents when typed inside `if` block
23257    cx.set_state(indoc! {"
23258        if [ \"$1\" = \"test\" ]; then
23259            echo \"foo bar\"
23260            ˇ
23261    "});
23262    cx.update_editor(|editor, window, cx| {
23263        editor.handle_input("else", window, cx);
23264    });
23265    cx.assert_editor_state(indoc! {"
23266        if [ \"$1\" = \"test\" ]; then
23267            echo \"foo bar\"
23268        elseˇ
23269    "});
23270
23271    // test `elif` auto outdents when typed inside `if` block
23272    cx.set_state(indoc! {"
23273        if [ \"$1\" = \"test\" ]; then
23274            echo \"foo bar\"
23275            ˇ
23276    "});
23277    cx.update_editor(|editor, window, cx| {
23278        editor.handle_input("elif", window, cx);
23279    });
23280    cx.assert_editor_state(indoc! {"
23281        if [ \"$1\" = \"test\" ]; then
23282            echo \"foo bar\"
23283        elifˇ
23284    "});
23285
23286    // test `fi` auto outdents when typed inside `else` block
23287    cx.set_state(indoc! {"
23288        if [ \"$1\" = \"test\" ]; then
23289            echo \"foo bar\"
23290        else
23291            echo \"bar baz\"
23292            ˇ
23293    "});
23294    cx.update_editor(|editor, window, cx| {
23295        editor.handle_input("fi", window, cx);
23296    });
23297    cx.assert_editor_state(indoc! {"
23298        if [ \"$1\" = \"test\" ]; then
23299            echo \"foo bar\"
23300        else
23301            echo \"bar baz\"
23302        fiˇ
23303    "});
23304
23305    // test `done` auto outdents when typed inside `while` block
23306    cx.set_state(indoc! {"
23307        while read line; do
23308            echo \"$line\"
23309            ˇ
23310    "});
23311    cx.update_editor(|editor, window, cx| {
23312        editor.handle_input("done", window, cx);
23313    });
23314    cx.assert_editor_state(indoc! {"
23315        while read line; do
23316            echo \"$line\"
23317        doneˇ
23318    "});
23319
23320    // test `done` auto outdents when typed inside `for` block
23321    cx.set_state(indoc! {"
23322        for file in *.txt; do
23323            cat \"$file\"
23324            ˇ
23325    "});
23326    cx.update_editor(|editor, window, cx| {
23327        editor.handle_input("done", window, cx);
23328    });
23329    cx.assert_editor_state(indoc! {"
23330        for file in *.txt; do
23331            cat \"$file\"
23332        doneˇ
23333    "});
23334
23335    // test `esac` auto outdents when typed inside `case` block
23336    cx.set_state(indoc! {"
23337        case \"$1\" in
23338            start)
23339                echo \"foo bar\"
23340                ;;
23341            stop)
23342                echo \"bar baz\"
23343                ;;
23344            ˇ
23345    "});
23346    cx.update_editor(|editor, window, cx| {
23347        editor.handle_input("esac", window, cx);
23348    });
23349    cx.assert_editor_state(indoc! {"
23350        case \"$1\" in
23351            start)
23352                echo \"foo bar\"
23353                ;;
23354            stop)
23355                echo \"bar baz\"
23356                ;;
23357        esacˇ
23358    "});
23359
23360    // test `*)` auto outdents when typed inside `case` block
23361    cx.set_state(indoc! {"
23362        case \"$1\" in
23363            start)
23364                echo \"foo bar\"
23365                ;;
23366                ˇ
23367    "});
23368    cx.update_editor(|editor, window, cx| {
23369        editor.handle_input("*)", window, cx);
23370    });
23371    cx.assert_editor_state(indoc! {"
23372        case \"$1\" in
23373            start)
23374                echo \"foo bar\"
23375                ;;
23376            *)ˇ
23377    "});
23378
23379    // test `fi` outdents to correct level with nested if blocks
23380    cx.set_state(indoc! {"
23381        if [ \"$1\" = \"test\" ]; then
23382            echo \"outer if\"
23383            if [ \"$2\" = \"debug\" ]; then
23384                echo \"inner if\"
23385                ˇ
23386    "});
23387    cx.update_editor(|editor, window, cx| {
23388        editor.handle_input("fi", window, cx);
23389    });
23390    cx.assert_editor_state(indoc! {"
23391        if [ \"$1\" = \"test\" ]; then
23392            echo \"outer if\"
23393            if [ \"$2\" = \"debug\" ]; then
23394                echo \"inner if\"
23395            fiˇ
23396    "});
23397}
23398
23399#[gpui::test]
23400async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
23401    init_test(cx, |_| {});
23402    update_test_language_settings(cx, |settings| {
23403        settings.defaults.extend_comment_on_newline = Some(false);
23404    });
23405    let mut cx = EditorTestContext::new(cx).await;
23406    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
23407    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23408
23409    // test correct indent after newline on comment
23410    cx.set_state(indoc! {"
23411        # COMMENT:ˇ
23412    "});
23413    cx.update_editor(|editor, window, cx| {
23414        editor.newline(&Newline, window, cx);
23415    });
23416    cx.assert_editor_state(indoc! {"
23417        # COMMENT:
23418        ˇ
23419    "});
23420
23421    // test correct indent after newline after `then`
23422    cx.set_state(indoc! {"
23423
23424        if [ \"$1\" = \"test\" ]; thenˇ
23425    "});
23426    cx.update_editor(|editor, window, cx| {
23427        editor.newline(&Newline, window, cx);
23428    });
23429    cx.run_until_parked();
23430    cx.assert_editor_state(indoc! {"
23431
23432        if [ \"$1\" = \"test\" ]; then
23433            ˇ
23434    "});
23435
23436    // test correct indent after newline after `else`
23437    cx.set_state(indoc! {"
23438        if [ \"$1\" = \"test\" ]; then
23439        elseˇ
23440    "});
23441    cx.update_editor(|editor, window, cx| {
23442        editor.newline(&Newline, window, cx);
23443    });
23444    cx.run_until_parked();
23445    cx.assert_editor_state(indoc! {"
23446        if [ \"$1\" = \"test\" ]; then
23447        else
23448            ˇ
23449    "});
23450
23451    // test correct indent after newline after `elif`
23452    cx.set_state(indoc! {"
23453        if [ \"$1\" = \"test\" ]; then
23454        elifˇ
23455    "});
23456    cx.update_editor(|editor, window, cx| {
23457        editor.newline(&Newline, window, cx);
23458    });
23459    cx.run_until_parked();
23460    cx.assert_editor_state(indoc! {"
23461        if [ \"$1\" = \"test\" ]; then
23462        elif
23463            ˇ
23464    "});
23465
23466    // test correct indent after newline after `do`
23467    cx.set_state(indoc! {"
23468        for file in *.txt; doˇ
23469    "});
23470    cx.update_editor(|editor, window, cx| {
23471        editor.newline(&Newline, window, cx);
23472    });
23473    cx.run_until_parked();
23474    cx.assert_editor_state(indoc! {"
23475        for file in *.txt; do
23476            ˇ
23477    "});
23478
23479    // test correct indent after newline after case pattern
23480    cx.set_state(indoc! {"
23481        case \"$1\" in
23482            start)ˇ
23483    "});
23484    cx.update_editor(|editor, window, cx| {
23485        editor.newline(&Newline, window, cx);
23486    });
23487    cx.run_until_parked();
23488    cx.assert_editor_state(indoc! {"
23489        case \"$1\" in
23490            start)
23491                ˇ
23492    "});
23493
23494    // test correct indent after newline after case pattern
23495    cx.set_state(indoc! {"
23496        case \"$1\" in
23497            start)
23498                ;;
23499            *)ˇ
23500    "});
23501    cx.update_editor(|editor, window, cx| {
23502        editor.newline(&Newline, window, cx);
23503    });
23504    cx.run_until_parked();
23505    cx.assert_editor_state(indoc! {"
23506        case \"$1\" in
23507            start)
23508                ;;
23509            *)
23510                ˇ
23511    "});
23512
23513    // test correct indent after newline after function opening brace
23514    cx.set_state(indoc! {"
23515        function test() {ˇ}
23516    "});
23517    cx.update_editor(|editor, window, cx| {
23518        editor.newline(&Newline, window, cx);
23519    });
23520    cx.run_until_parked();
23521    cx.assert_editor_state(indoc! {"
23522        function test() {
23523            ˇ
23524        }
23525    "});
23526
23527    // test no extra indent after semicolon on same line
23528    cx.set_state(indoc! {"
23529        echo \"test\"23530    "});
23531    cx.update_editor(|editor, window, cx| {
23532        editor.newline(&Newline, window, cx);
23533    });
23534    cx.run_until_parked();
23535    cx.assert_editor_state(indoc! {"
23536        echo \"test\";
23537        ˇ
23538    "});
23539}
23540
23541fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
23542    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
23543    point..point
23544}
23545
23546#[track_caller]
23547fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
23548    let (text, ranges) = marked_text_ranges(marked_text, true);
23549    assert_eq!(editor.text(cx), text);
23550    assert_eq!(
23551        editor.selections.ranges(cx),
23552        ranges,
23553        "Assert selections are {}",
23554        marked_text
23555    );
23556}
23557
23558pub fn handle_signature_help_request(
23559    cx: &mut EditorLspTestContext,
23560    mocked_response: lsp::SignatureHelp,
23561) -> impl Future<Output = ()> + use<> {
23562    let mut request =
23563        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
23564            let mocked_response = mocked_response.clone();
23565            async move { Ok(Some(mocked_response)) }
23566        });
23567
23568    async move {
23569        request.next().await;
23570    }
23571}
23572
23573#[track_caller]
23574pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
23575    cx.update_editor(|editor, _, _| {
23576        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
23577            let entries = menu.entries.borrow();
23578            let entries = entries
23579                .iter()
23580                .map(|entry| entry.string.as_str())
23581                .collect::<Vec<_>>();
23582            assert_eq!(entries, expected);
23583        } else {
23584            panic!("Expected completions menu");
23585        }
23586    });
23587}
23588
23589/// Handle completion request passing a marked string specifying where the completion
23590/// should be triggered from using '|' character, what range should be replaced, and what completions
23591/// should be returned using '<' and '>' to delimit the range.
23592///
23593/// Also see `handle_completion_request_with_insert_and_replace`.
23594#[track_caller]
23595pub fn handle_completion_request(
23596    marked_string: &str,
23597    completions: Vec<&'static str>,
23598    is_incomplete: bool,
23599    counter: Arc<AtomicUsize>,
23600    cx: &mut EditorLspTestContext,
23601) -> impl Future<Output = ()> {
23602    let complete_from_marker: TextRangeMarker = '|'.into();
23603    let replace_range_marker: TextRangeMarker = ('<', '>').into();
23604    let (_, mut marked_ranges) = marked_text_ranges_by(
23605        marked_string,
23606        vec![complete_from_marker.clone(), replace_range_marker.clone()],
23607    );
23608
23609    let complete_from_position =
23610        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
23611    let replace_range =
23612        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
23613
23614    let mut request =
23615        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
23616            let completions = completions.clone();
23617            counter.fetch_add(1, atomic::Ordering::Release);
23618            async move {
23619                assert_eq!(params.text_document_position.text_document.uri, url.clone());
23620                assert_eq!(
23621                    params.text_document_position.position,
23622                    complete_from_position
23623                );
23624                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
23625                    is_incomplete: is_incomplete,
23626                    item_defaults: None,
23627                    items: completions
23628                        .iter()
23629                        .map(|completion_text| lsp::CompletionItem {
23630                            label: completion_text.to_string(),
23631                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
23632                                range: replace_range,
23633                                new_text: completion_text.to_string(),
23634                            })),
23635                            ..Default::default()
23636                        })
23637                        .collect(),
23638                })))
23639            }
23640        });
23641
23642    async move {
23643        request.next().await;
23644    }
23645}
23646
23647/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
23648/// given instead, which also contains an `insert` range.
23649///
23650/// This function uses markers to define ranges:
23651/// - `|` marks the cursor position
23652/// - `<>` marks the replace range
23653/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
23654pub fn handle_completion_request_with_insert_and_replace(
23655    cx: &mut EditorLspTestContext,
23656    marked_string: &str,
23657    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
23658    counter: Arc<AtomicUsize>,
23659) -> impl Future<Output = ()> {
23660    let complete_from_marker: TextRangeMarker = '|'.into();
23661    let replace_range_marker: TextRangeMarker = ('<', '>').into();
23662    let insert_range_marker: TextRangeMarker = ('{', '}').into();
23663
23664    let (_, mut marked_ranges) = marked_text_ranges_by(
23665        marked_string,
23666        vec![
23667            complete_from_marker.clone(),
23668            replace_range_marker.clone(),
23669            insert_range_marker.clone(),
23670        ],
23671    );
23672
23673    let complete_from_position =
23674        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
23675    let replace_range =
23676        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
23677
23678    let insert_range = match marked_ranges.remove(&insert_range_marker) {
23679        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
23680        _ => lsp::Range {
23681            start: replace_range.start,
23682            end: complete_from_position,
23683        },
23684    };
23685
23686    let mut request =
23687        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
23688            let completions = completions.clone();
23689            counter.fetch_add(1, atomic::Ordering::Release);
23690            async move {
23691                assert_eq!(params.text_document_position.text_document.uri, url.clone());
23692                assert_eq!(
23693                    params.text_document_position.position, complete_from_position,
23694                    "marker `|` position doesn't match",
23695                );
23696                Ok(Some(lsp::CompletionResponse::Array(
23697                    completions
23698                        .iter()
23699                        .map(|(label, new_text)| lsp::CompletionItem {
23700                            label: label.to_string(),
23701                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23702                                lsp::InsertReplaceEdit {
23703                                    insert: insert_range,
23704                                    replace: replace_range,
23705                                    new_text: new_text.to_string(),
23706                                },
23707                            )),
23708                            ..Default::default()
23709                        })
23710                        .collect(),
23711                )))
23712            }
23713        });
23714
23715    async move {
23716        request.next().await;
23717    }
23718}
23719
23720fn handle_resolve_completion_request(
23721    cx: &mut EditorLspTestContext,
23722    edits: Option<Vec<(&'static str, &'static str)>>,
23723) -> impl Future<Output = ()> {
23724    let edits = edits.map(|edits| {
23725        edits
23726            .iter()
23727            .map(|(marked_string, new_text)| {
23728                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
23729                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
23730                lsp::TextEdit::new(replace_range, new_text.to_string())
23731            })
23732            .collect::<Vec<_>>()
23733    });
23734
23735    let mut request =
23736        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
23737            let edits = edits.clone();
23738            async move {
23739                Ok(lsp::CompletionItem {
23740                    additional_text_edits: edits,
23741                    ..Default::default()
23742                })
23743            }
23744        });
23745
23746    async move {
23747        request.next().await;
23748    }
23749}
23750
23751pub(crate) fn update_test_language_settings(
23752    cx: &mut TestAppContext,
23753    f: impl Fn(&mut AllLanguageSettingsContent),
23754) {
23755    cx.update(|cx| {
23756        SettingsStore::update_global(cx, |store, cx| {
23757            store.update_user_settings::<AllLanguageSettings>(cx, f);
23758        });
23759    });
23760}
23761
23762pub(crate) fn update_test_project_settings(
23763    cx: &mut TestAppContext,
23764    f: impl Fn(&mut ProjectSettings),
23765) {
23766    cx.update(|cx| {
23767        SettingsStore::update_global(cx, |store, cx| {
23768            store.update_user_settings::<ProjectSettings>(cx, f);
23769        });
23770    });
23771}
23772
23773pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
23774    cx.update(|cx| {
23775        assets::Assets.load_test_fonts(cx);
23776        let store = SettingsStore::test(cx);
23777        cx.set_global(store);
23778        theme::init(theme::LoadThemes::JustBase, cx);
23779        release_channel::init(SemanticVersion::default(), cx);
23780        client::init_settings(cx);
23781        language::init(cx);
23782        Project::init_settings(cx);
23783        workspace::init_settings(cx);
23784        crate::init(cx);
23785    });
23786    zlog::init_test();
23787    update_test_language_settings(cx, f);
23788}
23789
23790#[track_caller]
23791fn assert_hunk_revert(
23792    not_reverted_text_with_selections: &str,
23793    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
23794    expected_reverted_text_with_selections: &str,
23795    base_text: &str,
23796    cx: &mut EditorLspTestContext,
23797) {
23798    cx.set_state(not_reverted_text_with_selections);
23799    cx.set_head_text(base_text);
23800    cx.executor().run_until_parked();
23801
23802    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
23803        let snapshot = editor.snapshot(window, cx);
23804        let reverted_hunk_statuses = snapshot
23805            .buffer_snapshot
23806            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
23807            .map(|hunk| hunk.status().kind)
23808            .collect::<Vec<_>>();
23809
23810        editor.git_restore(&Default::default(), window, cx);
23811        reverted_hunk_statuses
23812    });
23813    cx.executor().run_until_parked();
23814    cx.assert_editor_state(expected_reverted_text_with_selections);
23815    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
23816}
23817
23818#[gpui::test(iterations = 10)]
23819async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
23820    init_test(cx, |_| {});
23821
23822    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
23823    let counter = diagnostic_requests.clone();
23824
23825    let fs = FakeFs::new(cx.executor());
23826    fs.insert_tree(
23827        path!("/a"),
23828        json!({
23829            "first.rs": "fn main() { let a = 5; }",
23830            "second.rs": "// Test file",
23831        }),
23832    )
23833    .await;
23834
23835    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23836    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23837    let cx = &mut VisualTestContext::from_window(*workspace, cx);
23838
23839    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23840    language_registry.add(rust_lang());
23841    let mut fake_servers = language_registry.register_fake_lsp(
23842        "Rust",
23843        FakeLspAdapter {
23844            capabilities: lsp::ServerCapabilities {
23845                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
23846                    lsp::DiagnosticOptions {
23847                        identifier: None,
23848                        inter_file_dependencies: true,
23849                        workspace_diagnostics: true,
23850                        work_done_progress_options: Default::default(),
23851                    },
23852                )),
23853                ..Default::default()
23854            },
23855            ..Default::default()
23856        },
23857    );
23858
23859    let editor = workspace
23860        .update(cx, |workspace, window, cx| {
23861            workspace.open_abs_path(
23862                PathBuf::from(path!("/a/first.rs")),
23863                OpenOptions::default(),
23864                window,
23865                cx,
23866            )
23867        })
23868        .unwrap()
23869        .await
23870        .unwrap()
23871        .downcast::<Editor>()
23872        .unwrap();
23873    let fake_server = fake_servers.next().await.unwrap();
23874    let server_id = fake_server.server.server_id();
23875    let mut first_request = fake_server
23876        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
23877            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
23878            let result_id = Some(new_result_id.to_string());
23879            assert_eq!(
23880                params.text_document.uri,
23881                lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
23882            );
23883            async move {
23884                Ok(lsp::DocumentDiagnosticReportResult::Report(
23885                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
23886                        related_documents: None,
23887                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
23888                            items: Vec::new(),
23889                            result_id,
23890                        },
23891                    }),
23892                ))
23893            }
23894        });
23895
23896    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
23897        project.update(cx, |project, cx| {
23898            let buffer_id = editor
23899                .read(cx)
23900                .buffer()
23901                .read(cx)
23902                .as_singleton()
23903                .expect("created a singleton buffer")
23904                .read(cx)
23905                .remote_id();
23906            let buffer_result_id = project
23907                .lsp_store()
23908                .read(cx)
23909                .result_id(server_id, buffer_id, cx);
23910            assert_eq!(expected, buffer_result_id);
23911        });
23912    };
23913
23914    ensure_result_id(None, cx);
23915    cx.executor().advance_clock(Duration::from_millis(60));
23916    cx.executor().run_until_parked();
23917    assert_eq!(
23918        diagnostic_requests.load(atomic::Ordering::Acquire),
23919        1,
23920        "Opening file should trigger diagnostic request"
23921    );
23922    first_request
23923        .next()
23924        .await
23925        .expect("should have sent the first diagnostics pull request");
23926    ensure_result_id(Some("1".to_string()), cx);
23927
23928    // Editing should trigger diagnostics
23929    editor.update_in(cx, |editor, window, cx| {
23930        editor.handle_input("2", window, cx)
23931    });
23932    cx.executor().advance_clock(Duration::from_millis(60));
23933    cx.executor().run_until_parked();
23934    assert_eq!(
23935        diagnostic_requests.load(atomic::Ordering::Acquire),
23936        2,
23937        "Editing should trigger diagnostic request"
23938    );
23939    ensure_result_id(Some("2".to_string()), cx);
23940
23941    // Moving cursor should not trigger diagnostic request
23942    editor.update_in(cx, |editor, window, cx| {
23943        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23944            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
23945        });
23946    });
23947    cx.executor().advance_clock(Duration::from_millis(60));
23948    cx.executor().run_until_parked();
23949    assert_eq!(
23950        diagnostic_requests.load(atomic::Ordering::Acquire),
23951        2,
23952        "Cursor movement should not trigger diagnostic request"
23953    );
23954    ensure_result_id(Some("2".to_string()), cx);
23955    // Multiple rapid edits should be debounced
23956    for _ in 0..5 {
23957        editor.update_in(cx, |editor, window, cx| {
23958            editor.handle_input("x", window, cx)
23959        });
23960    }
23961    cx.executor().advance_clock(Duration::from_millis(60));
23962    cx.executor().run_until_parked();
23963
23964    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
23965    assert!(
23966        final_requests <= 4,
23967        "Multiple rapid edits should be debounced (got {final_requests} requests)",
23968    );
23969    ensure_result_id(Some(final_requests.to_string()), cx);
23970}
23971
23972#[gpui::test]
23973async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
23974    // Regression test for issue #11671
23975    // Previously, adding a cursor after moving multiple cursors would reset
23976    // the cursor count instead of adding to the existing cursors.
23977    init_test(cx, |_| {});
23978    let mut cx = EditorTestContext::new(cx).await;
23979
23980    // Create a simple buffer with cursor at start
23981    cx.set_state(indoc! {"
23982        ˇaaaa
23983        bbbb
23984        cccc
23985        dddd
23986        eeee
23987        ffff
23988        gggg
23989        hhhh"});
23990
23991    // Add 2 cursors below (so we have 3 total)
23992    cx.update_editor(|editor, window, cx| {
23993        editor.add_selection_below(&Default::default(), window, cx);
23994        editor.add_selection_below(&Default::default(), window, cx);
23995    });
23996
23997    // Verify we have 3 cursors
23998    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
23999    assert_eq!(
24000        initial_count, 3,
24001        "Should have 3 cursors after adding 2 below"
24002    );
24003
24004    // Move down one line
24005    cx.update_editor(|editor, window, cx| {
24006        editor.move_down(&MoveDown, window, cx);
24007    });
24008
24009    // Add another cursor below
24010    cx.update_editor(|editor, window, cx| {
24011        editor.add_selection_below(&Default::default(), window, cx);
24012    });
24013
24014    // Should now have 4 cursors (3 original + 1 new)
24015    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
24016    assert_eq!(
24017        final_count, 4,
24018        "Should have 4 cursors after moving and adding another"
24019    );
24020}
24021
24022#[gpui::test(iterations = 10)]
24023async fn test_document_colors(cx: &mut TestAppContext) {
24024    let expected_color = Rgba {
24025        r: 0.33,
24026        g: 0.33,
24027        b: 0.33,
24028        a: 0.33,
24029    };
24030
24031    init_test(cx, |_| {});
24032
24033    let fs = FakeFs::new(cx.executor());
24034    fs.insert_tree(
24035        path!("/a"),
24036        json!({
24037            "first.rs": "fn main() { let a = 5; }",
24038        }),
24039    )
24040    .await;
24041
24042    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24043    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24044    let cx = &mut VisualTestContext::from_window(*workspace, cx);
24045
24046    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24047    language_registry.add(rust_lang());
24048    let mut fake_servers = language_registry.register_fake_lsp(
24049        "Rust",
24050        FakeLspAdapter {
24051            capabilities: lsp::ServerCapabilities {
24052                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
24053                ..lsp::ServerCapabilities::default()
24054            },
24055            name: "rust-analyzer",
24056            ..FakeLspAdapter::default()
24057        },
24058    );
24059    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
24060        "Rust",
24061        FakeLspAdapter {
24062            capabilities: lsp::ServerCapabilities {
24063                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
24064                ..lsp::ServerCapabilities::default()
24065            },
24066            name: "not-rust-analyzer",
24067            ..FakeLspAdapter::default()
24068        },
24069    );
24070
24071    let editor = workspace
24072        .update(cx, |workspace, window, cx| {
24073            workspace.open_abs_path(
24074                PathBuf::from(path!("/a/first.rs")),
24075                OpenOptions::default(),
24076                window,
24077                cx,
24078            )
24079        })
24080        .unwrap()
24081        .await
24082        .unwrap()
24083        .downcast::<Editor>()
24084        .unwrap();
24085    let fake_language_server = fake_servers.next().await.unwrap();
24086    let fake_language_server_without_capabilities =
24087        fake_servers_without_capabilities.next().await.unwrap();
24088    let requests_made = Arc::new(AtomicUsize::new(0));
24089    let closure_requests_made = Arc::clone(&requests_made);
24090    let mut color_request_handle = fake_language_server
24091        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
24092            let requests_made = Arc::clone(&closure_requests_made);
24093            async move {
24094                assert_eq!(
24095                    params.text_document.uri,
24096                    lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
24097                );
24098                requests_made.fetch_add(1, atomic::Ordering::Release);
24099                Ok(vec![
24100                    lsp::ColorInformation {
24101                        range: lsp::Range {
24102                            start: lsp::Position {
24103                                line: 0,
24104                                character: 0,
24105                            },
24106                            end: lsp::Position {
24107                                line: 0,
24108                                character: 1,
24109                            },
24110                        },
24111                        color: lsp::Color {
24112                            red: 0.33,
24113                            green: 0.33,
24114                            blue: 0.33,
24115                            alpha: 0.33,
24116                        },
24117                    },
24118                    lsp::ColorInformation {
24119                        range: lsp::Range {
24120                            start: lsp::Position {
24121                                line: 0,
24122                                character: 0,
24123                            },
24124                            end: lsp::Position {
24125                                line: 0,
24126                                character: 1,
24127                            },
24128                        },
24129                        color: lsp::Color {
24130                            red: 0.33,
24131                            green: 0.33,
24132                            blue: 0.33,
24133                            alpha: 0.33,
24134                        },
24135                    },
24136                ])
24137            }
24138        });
24139
24140    let _handle = fake_language_server_without_capabilities
24141        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
24142            panic!("Should not be called");
24143        });
24144    cx.executor().advance_clock(Duration::from_millis(100));
24145    color_request_handle.next().await.unwrap();
24146    cx.run_until_parked();
24147    assert_eq!(
24148        1,
24149        requests_made.load(atomic::Ordering::Acquire),
24150        "Should query for colors once per editor open"
24151    );
24152    editor.update_in(cx, |editor, _, cx| {
24153        assert_eq!(
24154            vec![expected_color],
24155            extract_color_inlays(editor, cx),
24156            "Should have an initial inlay"
24157        );
24158    });
24159
24160    // opening another file in a split should not influence the LSP query counter
24161    workspace
24162        .update(cx, |workspace, window, cx| {
24163            assert_eq!(
24164                workspace.panes().len(),
24165                1,
24166                "Should have one pane with one editor"
24167            );
24168            workspace.move_item_to_pane_in_direction(
24169                &MoveItemToPaneInDirection {
24170                    direction: SplitDirection::Right,
24171                    focus: false,
24172                    clone: true,
24173                },
24174                window,
24175                cx,
24176            );
24177        })
24178        .unwrap();
24179    cx.run_until_parked();
24180    workspace
24181        .update(cx, |workspace, _, cx| {
24182            let panes = workspace.panes();
24183            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
24184            for pane in panes {
24185                let editor = pane
24186                    .read(cx)
24187                    .active_item()
24188                    .and_then(|item| item.downcast::<Editor>())
24189                    .expect("Should have opened an editor in each split");
24190                let editor_file = editor
24191                    .read(cx)
24192                    .buffer()
24193                    .read(cx)
24194                    .as_singleton()
24195                    .expect("test deals with singleton buffers")
24196                    .read(cx)
24197                    .file()
24198                    .expect("test buffese should have a file")
24199                    .path();
24200                assert_eq!(
24201                    editor_file.as_ref(),
24202                    Path::new("first.rs"),
24203                    "Both editors should be opened for the same file"
24204                )
24205            }
24206        })
24207        .unwrap();
24208
24209    cx.executor().advance_clock(Duration::from_millis(500));
24210    let save = editor.update_in(cx, |editor, window, cx| {
24211        editor.move_to_end(&MoveToEnd, window, cx);
24212        editor.handle_input("dirty", window, cx);
24213        editor.save(
24214            SaveOptions {
24215                format: true,
24216                autosave: true,
24217            },
24218            project.clone(),
24219            window,
24220            cx,
24221        )
24222    });
24223    save.await.unwrap();
24224
24225    color_request_handle.next().await.unwrap();
24226    cx.run_until_parked();
24227    assert_eq!(
24228        3,
24229        requests_made.load(atomic::Ordering::Acquire),
24230        "Should query for colors once per save and once per formatting after save"
24231    );
24232
24233    drop(editor);
24234    let close = workspace
24235        .update(cx, |workspace, window, cx| {
24236            workspace.active_pane().update(cx, |pane, cx| {
24237                pane.close_active_item(&CloseActiveItem::default(), window, cx)
24238            })
24239        })
24240        .unwrap();
24241    close.await.unwrap();
24242    let close = workspace
24243        .update(cx, |workspace, window, cx| {
24244            workspace.active_pane().update(cx, |pane, cx| {
24245                pane.close_active_item(&CloseActiveItem::default(), window, cx)
24246            })
24247        })
24248        .unwrap();
24249    close.await.unwrap();
24250    assert_eq!(
24251        3,
24252        requests_made.load(atomic::Ordering::Acquire),
24253        "After saving and closing all editors, no extra requests should be made"
24254    );
24255    workspace
24256        .update(cx, |workspace, _, cx| {
24257            assert!(
24258                workspace.active_item(cx).is_none(),
24259                "Should close all editors"
24260            )
24261        })
24262        .unwrap();
24263
24264    workspace
24265        .update(cx, |workspace, window, cx| {
24266            workspace.active_pane().update(cx, |pane, cx| {
24267                pane.navigate_backward(window, cx);
24268            })
24269        })
24270        .unwrap();
24271    cx.executor().advance_clock(Duration::from_millis(100));
24272    cx.run_until_parked();
24273    let editor = workspace
24274        .update(cx, |workspace, _, cx| {
24275            workspace
24276                .active_item(cx)
24277                .expect("Should have reopened the editor again after navigating back")
24278                .downcast::<Editor>()
24279                .expect("Should be an editor")
24280        })
24281        .unwrap();
24282    color_request_handle.next().await.unwrap();
24283    assert_eq!(
24284        3,
24285        requests_made.load(atomic::Ordering::Acquire),
24286        "Cache should be reused on buffer close and reopen"
24287    );
24288    editor.update(cx, |editor, cx| {
24289        assert_eq!(
24290            vec![expected_color],
24291            extract_color_inlays(editor, cx),
24292            "Should have an initial inlay"
24293        );
24294    });
24295}
24296
24297#[gpui::test]
24298async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
24299    init_test(cx, |_| {});
24300    let (editor, cx) = cx.add_window_view(Editor::single_line);
24301    editor.update_in(cx, |editor, window, cx| {
24302        editor.set_text("oops\n\nwow\n", window, cx)
24303    });
24304    cx.run_until_parked();
24305    editor.update(cx, |editor, cx| {
24306        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
24307    });
24308    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
24309    cx.run_until_parked();
24310    editor.update(cx, |editor, cx| {
24311        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
24312    });
24313}
24314
24315#[track_caller]
24316fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
24317    editor
24318        .all_inlays(cx)
24319        .into_iter()
24320        .filter_map(|inlay| inlay.get_color())
24321        .map(Rgba::from)
24322        .collect()
24323}