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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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,
 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,
 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,
 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,
 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);
 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())
 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())
 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,
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);
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)
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, 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 = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
15754    let mut request =
15755        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
15756            let buffer_cloned = buffer_cloned.clone();
15757            async move {
15758                buffer_cloned.update(&mut cx, |buffer, _| {
15759                    assert_eq!(
15760                        buffer.text(),
15761                        "fn c() {\n    d()\n        .\n}\n",
15762                        "OnTypeFormatting should triggered after autoindent applied"
15763                    )
15764                })?;
15765
15766                Ok(Some(vec![]))
15767            }
15768        });
15769
15770    cx.simulate_keystroke(".");
15771    cx.run_until_parked();
15772
15773    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
15774    assert!(request.next().await.is_some());
15775    request.close();
15776    assert!(request.next().await.is_none());
15777}
15778
15779#[gpui::test]
15780async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
15781    init_test(cx, |_| {});
15782
15783    let fs = FakeFs::new(cx.executor());
15784    fs.insert_tree(
15785        path!("/a"),
15786        json!({
15787            "main.rs": "fn main() { let a = 5; }",
15788            "other.rs": "// Test file",
15789        }),
15790    )
15791    .await;
15792
15793    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15794
15795    let server_restarts = Arc::new(AtomicUsize::new(0));
15796    let closure_restarts = Arc::clone(&server_restarts);
15797    let language_server_name = "test language server";
15798    let language_name: LanguageName = "Rust".into();
15799
15800    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15801    language_registry.add(Arc::new(Language::new(
15802        LanguageConfig {
15803            name: language_name.clone(),
15804            matcher: LanguageMatcher {
15805                path_suffixes: vec!["rs".to_string()],
15806                ..Default::default()
15807            },
15808            ..Default::default()
15809        },
15810        Some(tree_sitter_rust::LANGUAGE.into()),
15811    )));
15812    let mut fake_servers = language_registry.register_fake_lsp(
15813        "Rust",
15814        FakeLspAdapter {
15815            name: language_server_name,
15816            initialization_options: Some(json!({
15817                "testOptionValue": true
15818            })),
15819            initializer: Some(Box::new(move |fake_server| {
15820                let task_restarts = Arc::clone(&closure_restarts);
15821                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
15822                    task_restarts.fetch_add(1, atomic::Ordering::Release);
15823                    futures::future::ready(Ok(()))
15824                });
15825            })),
15826            ..Default::default()
15827        },
15828    );
15829
15830    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15831    let _buffer = project
15832        .update(cx, |project, cx| {
15833            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
15834        })
15835        .await
15836        .unwrap();
15837    let _fake_server = fake_servers.next().await.unwrap();
15838    update_test_language_settings(cx, |language_settings| {
15839        language_settings.languages.0.insert(
15840            language_name.clone(),
15841            LanguageSettingsContent {
15842                tab_size: NonZeroU32::new(8),
15843                ..Default::default()
15844            },
15845        );
15846    });
15847    cx.executor().run_until_parked();
15848    assert_eq!(
15849        server_restarts.load(atomic::Ordering::Acquire),
15850        0,
15851        "Should not restart LSP server on an unrelated change"
15852    );
15853
15854    update_test_project_settings(cx, |project_settings| {
15855        project_settings.lsp.insert(
15856            "Some other server name".into(),
15857            LspSettings {
15858                binary: None,
15859                settings: None,
15860                initialization_options: Some(json!({
15861                    "some other init value": false
15862                })),
15863                enable_lsp_tasks: false,
15864            },
15865        );
15866    });
15867    cx.executor().run_until_parked();
15868    assert_eq!(
15869        server_restarts.load(atomic::Ordering::Acquire),
15870        0,
15871        "Should not restart LSP server on an unrelated LSP settings change"
15872    );
15873
15874    update_test_project_settings(cx, |project_settings| {
15875        project_settings.lsp.insert(
15876            language_server_name.into(),
15877            LspSettings {
15878                binary: None,
15879                settings: None,
15880                initialization_options: Some(json!({
15881                    "anotherInitValue": false
15882                })),
15883                enable_lsp_tasks: false,
15884            },
15885        );
15886    });
15887    cx.executor().run_until_parked();
15888    assert_eq!(
15889        server_restarts.load(atomic::Ordering::Acquire),
15890        1,
15891        "Should restart LSP server on a related LSP settings change"
15892    );
15893
15894    update_test_project_settings(cx, |project_settings| {
15895        project_settings.lsp.insert(
15896            language_server_name.into(),
15897            LspSettings {
15898                binary: None,
15899                settings: None,
15900                initialization_options: Some(json!({
15901                    "anotherInitValue": false
15902                })),
15903                enable_lsp_tasks: false,
15904            },
15905        );
15906    });
15907    cx.executor().run_until_parked();
15908    assert_eq!(
15909        server_restarts.load(atomic::Ordering::Acquire),
15910        1,
15911        "Should not restart LSP server on a related LSP settings change that is the same"
15912    );
15913
15914    update_test_project_settings(cx, |project_settings| {
15915        project_settings.lsp.insert(
15916            language_server_name.into(),
15917            LspSettings {
15918                binary: None,
15919                settings: None,
15920                initialization_options: None,
15921                enable_lsp_tasks: false,
15922            },
15923        );
15924    });
15925    cx.executor().run_until_parked();
15926    assert_eq!(
15927        server_restarts.load(atomic::Ordering::Acquire),
15928        2,
15929        "Should restart LSP server on another related LSP settings change"
15930    );
15931}
15932
15933#[gpui::test]
15934async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
15935    init_test(cx, |_| {});
15936
15937    let mut cx = EditorLspTestContext::new_rust(
15938        lsp::ServerCapabilities {
15939            completion_provider: Some(lsp::CompletionOptions {
15940                trigger_characters: Some(vec![".".to_string()]),
15941                resolve_provider: Some(true),
15942                ..Default::default()
15943            }),
15944            ..Default::default()
15945        },
15946        cx,
15947    )
15948    .await;
15949
15950    cx.set_state("fn main() { let a = 2ˇ; }");
15951    cx.simulate_keystroke(".");
15952    let completion_item = lsp::CompletionItem {
15953        label: "some".into(),
15954        kind: Some(lsp::CompletionItemKind::SNIPPET),
15955        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15956        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15957            kind: lsp::MarkupKind::Markdown,
15958            value: "```rust\nSome(2)\n```".to_string(),
15959        })),
15960        deprecated: Some(false),
15961        sort_text: Some("fffffff2".to_string()),
15962        filter_text: Some("some".to_string()),
15963        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15964        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15965            range: lsp::Range {
15966                start: lsp::Position {
15967                    line: 0,
15968                    character: 22,
15969                },
15970                end: lsp::Position {
15971                    line: 0,
15972                    character: 22,
15973                },
15974            },
15975            new_text: "Some(2)".to_string(),
15976        })),
15977        additional_text_edits: Some(vec![lsp::TextEdit {
15978            range: lsp::Range {
15979                start: lsp::Position {
15980                    line: 0,
15981                    character: 20,
15982                },
15983                end: lsp::Position {
15984                    line: 0,
15985                    character: 22,
15986                },
15987            },
15988            new_text: "".to_string(),
15989        }]),
15990        ..Default::default()
15991    };
15992
15993    let closure_completion_item = completion_item.clone();
15994    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15995        let task_completion_item = closure_completion_item.clone();
15996        async move {
15997            Ok(Some(lsp::CompletionResponse::Array(vec![
15998                task_completion_item,
15999            ])))
16000        }
16001    });
16002
16003    request.next().await;
16004
16005    cx.condition(|editor, _| editor.context_menu_visible())
16006        .await;
16007    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
16008        editor
16009            .confirm_completion(&ConfirmCompletion::default(), window, cx)
16010            .unwrap()
16011    });
16012    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
16013
16014    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
16015        let task_completion_item = completion_item.clone();
16016        async move { Ok(task_completion_item) }
16017    })
16018    .next()
16019    .await
16020    .unwrap();
16021    apply_additional_edits.await.unwrap();
16022    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
16023}
16024
16025#[gpui::test]
16026async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
16027    init_test(cx, |_| {});
16028
16029    let mut cx = EditorLspTestContext::new_rust(
16030        lsp::ServerCapabilities {
16031            completion_provider: Some(lsp::CompletionOptions {
16032                trigger_characters: Some(vec![".".to_string()]),
16033                resolve_provider: Some(true),
16034                ..Default::default()
16035            }),
16036            ..Default::default()
16037        },
16038        cx,
16039    )
16040    .await;
16041
16042    cx.set_state("fn main() { let a = 2ˇ; }");
16043    cx.simulate_keystroke(".");
16044
16045    let item1 = lsp::CompletionItem {
16046        label: "method id()".to_string(),
16047        filter_text: Some("id".to_string()),
16048        detail: None,
16049        documentation: None,
16050        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16051            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16052            new_text: ".id".to_string(),
16053        })),
16054        ..lsp::CompletionItem::default()
16055    };
16056
16057    let item2 = lsp::CompletionItem {
16058        label: "other".to_string(),
16059        filter_text: Some("other".to_string()),
16060        detail: None,
16061        documentation: None,
16062        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16063            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16064            new_text: ".other".to_string(),
16065        })),
16066        ..lsp::CompletionItem::default()
16067    };
16068
16069    let item1 = item1.clone();
16070    cx.set_request_handler::<lsp::request::Completion, _, _>({
16071        let item1 = item1.clone();
16072        move |_, _, _| {
16073            let item1 = item1.clone();
16074            let item2 = item2.clone();
16075            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
16076        }
16077    })
16078    .next()
16079    .await;
16080
16081    cx.condition(|editor, _| editor.context_menu_visible())
16082        .await;
16083    cx.update_editor(|editor, _, _| {
16084        let context_menu = editor.context_menu.borrow_mut();
16085        let context_menu = context_menu
16086            .as_ref()
16087            .expect("Should have the context menu deployed");
16088        match context_menu {
16089            CodeContextMenu::Completions(completions_menu) => {
16090                let completions = completions_menu.completions.borrow_mut();
16091                assert_eq!(
16092                    completions
16093                        .iter()
16094                        .map(|completion| &completion.label.text)
16095                        .collect::<Vec<_>>(),
16096                    vec!["method id()", "other"]
16097                )
16098            }
16099            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
16100        }
16101    });
16102
16103    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
16104        let item1 = item1.clone();
16105        move |_, item_to_resolve, _| {
16106            let item1 = item1.clone();
16107            async move {
16108                if item1 == item_to_resolve {
16109                    Ok(lsp::CompletionItem {
16110                        label: "method id()".to_string(),
16111                        filter_text: Some("id".to_string()),
16112                        detail: Some("Now resolved!".to_string()),
16113                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
16114                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16115                            range: lsp::Range::new(
16116                                lsp::Position::new(0, 22),
16117                                lsp::Position::new(0, 22),
16118                            ),
16119                            new_text: ".id".to_string(),
16120                        })),
16121                        ..lsp::CompletionItem::default()
16122                    })
16123                } else {
16124                    Ok(item_to_resolve)
16125                }
16126            }
16127        }
16128    })
16129    .next()
16130    .await
16131    .unwrap();
16132    cx.run_until_parked();
16133
16134    cx.update_editor(|editor, window, cx| {
16135        editor.context_menu_next(&Default::default(), window, cx);
16136    });
16137
16138    cx.update_editor(|editor, _, _| {
16139        let context_menu = editor.context_menu.borrow_mut();
16140        let context_menu = context_menu
16141            .as_ref()
16142            .expect("Should have the context menu deployed");
16143        match context_menu {
16144            CodeContextMenu::Completions(completions_menu) => {
16145                let completions = completions_menu.completions.borrow_mut();
16146                assert_eq!(
16147                    completions
16148                        .iter()
16149                        .map(|completion| &completion.label.text)
16150                        .collect::<Vec<_>>(),
16151                    vec!["method id() Now resolved!", "other"],
16152                    "Should update first completion label, but not second as the filter text did not match."
16153                );
16154            }
16155            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
16156        }
16157    });
16158}
16159
16160#[gpui::test]
16161async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
16162    init_test(cx, |_| {});
16163    let mut cx = EditorLspTestContext::new_rust(
16164        lsp::ServerCapabilities {
16165            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
16166            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
16167            completion_provider: Some(lsp::CompletionOptions {
16168                resolve_provider: Some(true),
16169                ..Default::default()
16170            }),
16171            ..Default::default()
16172        },
16173        cx,
16174    )
16175    .await;
16176    cx.set_state(indoc! {"
16177        struct TestStruct {
16178            field: i32
16179        }
16180
16181        fn mainˇ() {
16182            let unused_var = 42;
16183            let test_struct = TestStruct { field: 42 };
16184        }
16185    "});
16186    let symbol_range = cx.lsp_range(indoc! {"
16187        struct TestStruct {
16188            field: i32
16189        }
16190
16191        «fn main»() {
16192            let unused_var = 42;
16193            let test_struct = TestStruct { field: 42 };
16194        }
16195    "});
16196    let mut hover_requests =
16197        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
16198            Ok(Some(lsp::Hover {
16199                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
16200                    kind: lsp::MarkupKind::Markdown,
16201                    value: "Function documentation".to_string(),
16202                }),
16203                range: Some(symbol_range),
16204            }))
16205        });
16206
16207    // Case 1: Test that code action menu hide hover popover
16208    cx.dispatch_action(Hover);
16209    hover_requests.next().await;
16210    cx.condition(|editor, _| editor.hover_state.visible()).await;
16211    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
16212        move |_, _, _| async move {
16213            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
16214                lsp::CodeAction {
16215                    title: "Remove unused variable".to_string(),
16216                    kind: Some(CodeActionKind::QUICKFIX),
16217                    edit: Some(lsp::WorkspaceEdit {
16218                        changes: Some(
16219                            [(
16220                                lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
16221                                vec![lsp::TextEdit {
16222                                    range: lsp::Range::new(
16223                                        lsp::Position::new(5, 4),
16224                                        lsp::Position::new(5, 27),
16225                                    ),
16226                                    new_text: "".to_string(),
16227                                }],
16228                            )]
16229                            .into_iter()
16230                            .collect(),
16231                        ),
16232                        ..Default::default()
16233                    }),
16234                    ..Default::default()
16235                },
16236            )]))
16237        },
16238    );
16239    cx.update_editor(|editor, window, cx| {
16240        editor.toggle_code_actions(
16241            &ToggleCodeActions {
16242                deployed_from: None,
16243                quick_launch: false,
16244            },
16245            window,
16246            cx,
16247        );
16248    });
16249    code_action_requests.next().await;
16250    cx.run_until_parked();
16251    cx.condition(|editor, _| editor.context_menu_visible())
16252        .await;
16253    cx.update_editor(|editor, _, _| {
16254        assert!(
16255            !editor.hover_state.visible(),
16256            "Hover popover should be hidden when code action menu is shown"
16257        );
16258        // Hide code actions
16259        editor.context_menu.take();
16260    });
16261
16262    // Case 2: Test that code completions hide hover popover
16263    cx.dispatch_action(Hover);
16264    hover_requests.next().await;
16265    cx.condition(|editor, _| editor.hover_state.visible()).await;
16266    let counter = Arc::new(AtomicUsize::new(0));
16267    let mut completion_requests =
16268        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16269            let counter = counter.clone();
16270            async move {
16271                counter.fetch_add(1, atomic::Ordering::Release);
16272                Ok(Some(lsp::CompletionResponse::Array(vec![
16273                    lsp::CompletionItem {
16274                        label: "main".into(),
16275                        kind: Some(lsp::CompletionItemKind::FUNCTION),
16276                        detail: Some("() -> ()".to_string()),
16277                        ..Default::default()
16278                    },
16279                    lsp::CompletionItem {
16280                        label: "TestStruct".into(),
16281                        kind: Some(lsp::CompletionItemKind::STRUCT),
16282                        detail: Some("struct TestStruct".to_string()),
16283                        ..Default::default()
16284                    },
16285                ])))
16286            }
16287        });
16288    cx.update_editor(|editor, window, cx| {
16289        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
16290    });
16291    completion_requests.next().await;
16292    cx.condition(|editor, _| editor.context_menu_visible())
16293        .await;
16294    cx.update_editor(|editor, _, _| {
16295        assert!(
16296            !editor.hover_state.visible(),
16297            "Hover popover should be hidden when completion menu is shown"
16298        );
16299    });
16300}
16301
16302#[gpui::test]
16303async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
16304    init_test(cx, |_| {});
16305
16306    let mut cx = EditorLspTestContext::new_rust(
16307        lsp::ServerCapabilities {
16308            completion_provider: Some(lsp::CompletionOptions {
16309                trigger_characters: Some(vec![".".to_string()]),
16310                resolve_provider: Some(true),
16311                ..Default::default()
16312            }),
16313            ..Default::default()
16314        },
16315        cx,
16316    )
16317    .await;
16318
16319    cx.set_state("fn main() { let a = 2ˇ; }");
16320    cx.simulate_keystroke(".");
16321
16322    let unresolved_item_1 = lsp::CompletionItem {
16323        label: "id".to_string(),
16324        filter_text: Some("id".to_string()),
16325        detail: None,
16326        documentation: None,
16327        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16328            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16329            new_text: ".id".to_string(),
16330        })),
16331        ..lsp::CompletionItem::default()
16332    };
16333    let resolved_item_1 = lsp::CompletionItem {
16334        additional_text_edits: Some(vec![lsp::TextEdit {
16335            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
16336            new_text: "!!".to_string(),
16337        }]),
16338        ..unresolved_item_1.clone()
16339    };
16340    let unresolved_item_2 = lsp::CompletionItem {
16341        label: "other".to_string(),
16342        filter_text: Some("other".to_string()),
16343        detail: None,
16344        documentation: None,
16345        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16346            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16347            new_text: ".other".to_string(),
16348        })),
16349        ..lsp::CompletionItem::default()
16350    };
16351    let resolved_item_2 = lsp::CompletionItem {
16352        additional_text_edits: Some(vec![lsp::TextEdit {
16353            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
16354            new_text: "??".to_string(),
16355        }]),
16356        ..unresolved_item_2.clone()
16357    };
16358
16359    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
16360    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
16361    cx.lsp
16362        .server
16363        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
16364            let unresolved_item_1 = unresolved_item_1.clone();
16365            let resolved_item_1 = resolved_item_1.clone();
16366            let unresolved_item_2 = unresolved_item_2.clone();
16367            let resolved_item_2 = resolved_item_2.clone();
16368            let resolve_requests_1 = resolve_requests_1.clone();
16369            let resolve_requests_2 = resolve_requests_2.clone();
16370            move |unresolved_request, _| {
16371                let unresolved_item_1 = unresolved_item_1.clone();
16372                let resolved_item_1 = resolved_item_1.clone();
16373                let unresolved_item_2 = unresolved_item_2.clone();
16374                let resolved_item_2 = resolved_item_2.clone();
16375                let resolve_requests_1 = resolve_requests_1.clone();
16376                let resolve_requests_2 = resolve_requests_2.clone();
16377                async move {
16378                    if unresolved_request == unresolved_item_1 {
16379                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
16380                        Ok(resolved_item_1.clone())
16381                    } else if unresolved_request == unresolved_item_2 {
16382                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
16383                        Ok(resolved_item_2.clone())
16384                    } else {
16385                        panic!("Unexpected completion item {unresolved_request:?}")
16386                    }
16387                }
16388            }
16389        })
16390        .detach();
16391
16392    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16393        let unresolved_item_1 = unresolved_item_1.clone();
16394        let unresolved_item_2 = unresolved_item_2.clone();
16395        async move {
16396            Ok(Some(lsp::CompletionResponse::Array(vec![
16397                unresolved_item_1,
16398                unresolved_item_2,
16399            ])))
16400        }
16401    })
16402    .next()
16403    .await;
16404
16405    cx.condition(|editor, _| editor.context_menu_visible())
16406        .await;
16407    cx.update_editor(|editor, _, _| {
16408        let context_menu = editor.context_menu.borrow_mut();
16409        let context_menu = context_menu
16410            .as_ref()
16411            .expect("Should have the context menu deployed");
16412        match context_menu {
16413            CodeContextMenu::Completions(completions_menu) => {
16414                let completions = completions_menu.completions.borrow_mut();
16415                assert_eq!(
16416                    completions
16417                        .iter()
16418                        .map(|completion| &completion.label.text)
16419                        .collect::<Vec<_>>(),
16420                    vec!["id", "other"]
16421                )
16422            }
16423            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
16424        }
16425    });
16426    cx.run_until_parked();
16427
16428    cx.update_editor(|editor, window, cx| {
16429        editor.context_menu_next(&ContextMenuNext, window, cx);
16430    });
16431    cx.run_until_parked();
16432    cx.update_editor(|editor, window, cx| {
16433        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
16434    });
16435    cx.run_until_parked();
16436    cx.update_editor(|editor, window, cx| {
16437        editor.context_menu_next(&ContextMenuNext, window, cx);
16438    });
16439    cx.run_until_parked();
16440    cx.update_editor(|editor, window, cx| {
16441        editor
16442            .compose_completion(&ComposeCompletion::default(), window, cx)
16443            .expect("No task returned")
16444    })
16445    .await
16446    .expect("Completion failed");
16447    cx.run_until_parked();
16448
16449    cx.update_editor(|editor, _, cx| {
16450        assert_eq!(
16451            resolve_requests_1.load(atomic::Ordering::Acquire),
16452            1,
16453            "Should always resolve once despite multiple selections"
16454        );
16455        assert_eq!(
16456            resolve_requests_2.load(atomic::Ordering::Acquire),
16457            1,
16458            "Should always resolve once after multiple selections and applying the completion"
16459        );
16460        assert_eq!(
16461            editor.text(cx),
16462            "fn main() { let a = ??.other; }",
16463            "Should use resolved data when applying the completion"
16464        );
16465    });
16466}
16467
16468#[gpui::test]
16469async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
16470    init_test(cx, |_| {});
16471
16472    let item_0 = lsp::CompletionItem {
16473        label: "abs".into(),
16474        insert_text: Some("abs".into()),
16475        data: Some(json!({ "very": "special"})),
16476        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
16477        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
16478            lsp::InsertReplaceEdit {
16479                new_text: "abs".to_string(),
16480                insert: lsp::Range::default(),
16481                replace: lsp::Range::default(),
16482            },
16483        )),
16484        ..lsp::CompletionItem::default()
16485    };
16486    let items = iter::once(item_0.clone())
16487        .chain((11..51).map(|i| lsp::CompletionItem {
16488            label: format!("item_{}", i),
16489            insert_text: Some(format!("item_{}", i)),
16490            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
16491            ..lsp::CompletionItem::default()
16492        }))
16493        .collect::<Vec<_>>();
16494
16495    let default_commit_characters = vec!["?".to_string()];
16496    let default_data = json!({ "default": "data"});
16497    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
16498    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
16499    let default_edit_range = lsp::Range {
16500        start: lsp::Position {
16501            line: 0,
16502            character: 5,
16503        },
16504        end: lsp::Position {
16505            line: 0,
16506            character: 5,
16507        },
16508    };
16509
16510    let mut cx = EditorLspTestContext::new_rust(
16511        lsp::ServerCapabilities {
16512            completion_provider: Some(lsp::CompletionOptions {
16513                trigger_characters: Some(vec![".".to_string()]),
16514                resolve_provider: Some(true),
16515                ..Default::default()
16516            }),
16517            ..Default::default()
16518        },
16519        cx,
16520    )
16521    .await;
16522
16523    cx.set_state("fn main() { let a = 2ˇ; }");
16524    cx.simulate_keystroke(".");
16525
16526    let completion_data = default_data.clone();
16527    let completion_characters = default_commit_characters.clone();
16528    let completion_items = items.clone();
16529    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16530        let default_data = completion_data.clone();
16531        let default_commit_characters = completion_characters.clone();
16532        let items = completion_items.clone();
16533        async move {
16534            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16535                items,
16536                item_defaults: Some(lsp::CompletionListItemDefaults {
16537                    data: Some(default_data.clone()),
16538                    commit_characters: Some(default_commit_characters.clone()),
16539                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
16540                        default_edit_range,
16541                    )),
16542                    insert_text_format: Some(default_insert_text_format),
16543                    insert_text_mode: Some(default_insert_text_mode),
16544                }),
16545                ..lsp::CompletionList::default()
16546            })))
16547        }
16548    })
16549    .next()
16550    .await;
16551
16552    let resolved_items = Arc::new(Mutex::new(Vec::new()));
16553    cx.lsp
16554        .server
16555        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
16556            let closure_resolved_items = resolved_items.clone();
16557            move |item_to_resolve, _| {
16558                let closure_resolved_items = closure_resolved_items.clone();
16559                async move {
16560                    closure_resolved_items.lock().push(item_to_resolve.clone());
16561                    Ok(item_to_resolve)
16562                }
16563            }
16564        })
16565        .detach();
16566
16567    cx.condition(|editor, _| editor.context_menu_visible())
16568        .await;
16569    cx.run_until_parked();
16570    cx.update_editor(|editor, _, _| {
16571        let menu = editor.context_menu.borrow_mut();
16572        match menu.as_ref().expect("should have the completions menu") {
16573            CodeContextMenu::Completions(completions_menu) => {
16574                assert_eq!(
16575                    completions_menu
16576                        .entries
16577                        .borrow()
16578                        .iter()
16579                        .map(|mat| mat.string.clone())
16580                        .collect::<Vec<String>>(),
16581                    items
16582                        .iter()
16583                        .map(|completion| completion.label.clone())
16584                        .collect::<Vec<String>>()
16585                );
16586            }
16587            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
16588        }
16589    });
16590    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
16591    // with 4 from the end.
16592    assert_eq!(
16593        *resolved_items.lock(),
16594        [&items[0..16], &items[items.len() - 4..items.len()]]
16595            .concat()
16596            .iter()
16597            .cloned()
16598            .map(|mut item| {
16599                if item.data.is_none() {
16600                    item.data = Some(default_data.clone());
16601                }
16602                item
16603            })
16604            .collect::<Vec<lsp::CompletionItem>>(),
16605        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
16606    );
16607    resolved_items.lock().clear();
16608
16609    cx.update_editor(|editor, window, cx| {
16610        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
16611    });
16612    cx.run_until_parked();
16613    // Completions that have already been resolved are skipped.
16614    assert_eq!(
16615        *resolved_items.lock(),
16616        items[items.len() - 17..items.len() - 4]
16617            .iter()
16618            .cloned()
16619            .map(|mut item| {
16620                if item.data.is_none() {
16621                    item.data = Some(default_data.clone());
16622                }
16623                item
16624            })
16625            .collect::<Vec<lsp::CompletionItem>>()
16626    );
16627    resolved_items.lock().clear();
16628}
16629
16630#[gpui::test]
16631async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
16632    init_test(cx, |_| {});
16633
16634    let mut cx = EditorLspTestContext::new(
16635        Language::new(
16636            LanguageConfig {
16637                matcher: LanguageMatcher {
16638                    path_suffixes: vec!["jsx".into()],
16639                    ..Default::default()
16640                },
16641                overrides: [(
16642                    "element".into(),
16643                    LanguageConfigOverride {
16644                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
16645                        ..Default::default()
16646                    },
16647                )]
16648                .into_iter()
16649                .collect(),
16650                ..Default::default()
16651            },
16652            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
16653        )
16654        .with_override_query("(jsx_self_closing_element) @element")
16655        .unwrap(),
16656        lsp::ServerCapabilities {
16657            completion_provider: Some(lsp::CompletionOptions {
16658                trigger_characters: Some(vec![":".to_string()]),
16659                ..Default::default()
16660            }),
16661            ..Default::default()
16662        },
16663        cx,
16664    )
16665    .await;
16666
16667    cx.lsp
16668        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16669            Ok(Some(lsp::CompletionResponse::Array(vec![
16670                lsp::CompletionItem {
16671                    label: "bg-blue".into(),
16672                    ..Default::default()
16673                },
16674                lsp::CompletionItem {
16675                    label: "bg-red".into(),
16676                    ..Default::default()
16677                },
16678                lsp::CompletionItem {
16679                    label: "bg-yellow".into(),
16680                    ..Default::default()
16681                },
16682            ])))
16683        });
16684
16685    cx.set_state(r#"<p class="bgˇ" />"#);
16686
16687    // Trigger completion when typing a dash, because the dash is an extra
16688    // word character in the 'element' scope, which contains the cursor.
16689    cx.simulate_keystroke("-");
16690    cx.executor().run_until_parked();
16691    cx.update_editor(|editor, _, _| {
16692        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16693        {
16694            assert_eq!(
16695                completion_menu_entries(menu),
16696                &["bg-blue", "bg-red", "bg-yellow"]
16697            );
16698        } else {
16699            panic!("expected completion menu to be open");
16700        }
16701    });
16702
16703    cx.simulate_keystroke("l");
16704    cx.executor().run_until_parked();
16705    cx.update_editor(|editor, _, _| {
16706        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16707        {
16708            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
16709        } else {
16710            panic!("expected completion menu to be open");
16711        }
16712    });
16713
16714    // When filtering completions, consider the character after the '-' to
16715    // be the start of a subword.
16716    cx.set_state(r#"<p class="yelˇ" />"#);
16717    cx.simulate_keystroke("l");
16718    cx.executor().run_until_parked();
16719    cx.update_editor(|editor, _, _| {
16720        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16721        {
16722            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
16723        } else {
16724            panic!("expected completion menu to be open");
16725        }
16726    });
16727}
16728
16729fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
16730    let entries = menu.entries.borrow();
16731    entries.iter().map(|mat| mat.string.clone()).collect()
16732}
16733
16734#[gpui::test]
16735async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
16736    init_test(cx, |settings| {
16737        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
16738            Formatter::Prettier,
16739        )))
16740    });
16741
16742    let fs = FakeFs::new(cx.executor());
16743    fs.insert_file(path!("/file.ts"), Default::default()).await;
16744
16745    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
16746    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16747
16748    language_registry.add(Arc::new(Language::new(
16749        LanguageConfig {
16750            name: "TypeScript".into(),
16751            matcher: LanguageMatcher {
16752                path_suffixes: vec!["ts".to_string()],
16753                ..Default::default()
16754            },
16755            ..Default::default()
16756        },
16757        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
16758    )));
16759    update_test_language_settings(cx, |settings| {
16760        settings.defaults.prettier = Some(PrettierSettings {
16761            allowed: true,
16762            ..PrettierSettings::default()
16763        });
16764    });
16765
16766    let test_plugin = "test_plugin";
16767    let _ = language_registry.register_fake_lsp(
16768        "TypeScript",
16769        FakeLspAdapter {
16770            prettier_plugins: vec![test_plugin],
16771            ..Default::default()
16772        },
16773    );
16774
16775    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
16776    let buffer = project
16777        .update(cx, |project, cx| {
16778            project.open_local_buffer(path!("/file.ts"), cx)
16779        })
16780        .await
16781        .unwrap();
16782
16783    let buffer_text = "one\ntwo\nthree\n";
16784    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16785    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16786    editor.update_in(cx, |editor, window, cx| {
16787        editor.set_text(buffer_text, window, cx)
16788    });
16789
16790    editor
16791        .update_in(cx, |editor, window, cx| {
16792            editor.perform_format(
16793                project.clone(),
16794                FormatTrigger::Manual,
16795                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
16796                window,
16797                cx,
16798            )
16799        })
16800        .unwrap()
16801        .await;
16802    assert_eq!(
16803        editor.update(cx, |editor, cx| editor.text(cx)),
16804        buffer_text.to_string() + prettier_format_suffix,
16805        "Test prettier formatting was not applied to the original buffer text",
16806    );
16807
16808    update_test_language_settings(cx, |settings| {
16809        settings.defaults.formatter = Some(SelectedFormatter::Auto)
16810    });
16811    let format = editor.update_in(cx, |editor, window, cx| {
16812        editor.perform_format(
16813            project.clone(),
16814            FormatTrigger::Manual,
16815            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
16816            window,
16817            cx,
16818        )
16819    });
16820    format.await.unwrap();
16821    assert_eq!(
16822        editor.update(cx, |editor, cx| editor.text(cx)),
16823        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
16824        "Autoformatting (via test prettier) was not applied to the original buffer text",
16825    );
16826}
16827
16828#[gpui::test]
16829async fn test_addition_reverts(cx: &mut TestAppContext) {
16830    init_test(cx, |_| {});
16831    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16832    let base_text = indoc! {r#"
16833        struct Row;
16834        struct Row1;
16835        struct Row2;
16836
16837        struct Row4;
16838        struct Row5;
16839        struct Row6;
16840
16841        struct Row8;
16842        struct Row9;
16843        struct Row10;"#};
16844
16845    // When addition hunks are not adjacent to carets, no hunk revert is performed
16846    assert_hunk_revert(
16847        indoc! {r#"struct Row;
16848                   struct Row1;
16849                   struct Row1.1;
16850                   struct Row1.2;
16851                   struct Row2;ˇ
16852
16853                   struct Row4;
16854                   struct Row5;
16855                   struct Row6;
16856
16857                   struct Row8;
16858                   ˇstruct Row9;
16859                   struct Row9.1;
16860                   struct Row9.2;
16861                   struct Row9.3;
16862                   struct Row10;"#},
16863        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
16864        indoc! {r#"struct Row;
16865                   struct Row1;
16866                   struct Row1.1;
16867                   struct Row1.2;
16868                   struct Row2;ˇ
16869
16870                   struct Row4;
16871                   struct Row5;
16872                   struct Row6;
16873
16874                   struct Row8;
16875                   ˇstruct Row9;
16876                   struct Row9.1;
16877                   struct Row9.2;
16878                   struct Row9.3;
16879                   struct Row10;"#},
16880        base_text,
16881        &mut cx,
16882    );
16883    // Same for selections
16884    assert_hunk_revert(
16885        indoc! {r#"struct Row;
16886                   struct Row1;
16887                   struct Row2;
16888                   struct Row2.1;
16889                   struct Row2.2;
16890                   «ˇ
16891                   struct Row4;
16892                   struct» Row5;
16893                   «struct Row6;
16894                   ˇ»
16895                   struct Row9.1;
16896                   struct Row9.2;
16897                   struct Row9.3;
16898                   struct Row8;
16899                   struct Row9;
16900                   struct Row10;"#},
16901        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
16902        indoc! {r#"struct Row;
16903                   struct Row1;
16904                   struct Row2;
16905                   struct Row2.1;
16906                   struct Row2.2;
16907                   «ˇ
16908                   struct Row4;
16909                   struct» Row5;
16910                   «struct Row6;
16911                   ˇ»
16912                   struct Row9.1;
16913                   struct Row9.2;
16914                   struct Row9.3;
16915                   struct Row8;
16916                   struct Row9;
16917                   struct Row10;"#},
16918        base_text,
16919        &mut cx,
16920    );
16921
16922    // When carets and selections intersect the addition hunks, those are reverted.
16923    // Adjacent carets got merged.
16924    assert_hunk_revert(
16925        indoc! {r#"struct Row;
16926                   ˇ// something on the top
16927                   struct Row1;
16928                   struct Row2;
16929                   struct Roˇw3.1;
16930                   struct Row2.2;
16931                   struct Row2.3;ˇ
16932
16933                   struct Row4;
16934                   struct ˇRow5.1;
16935                   struct Row5.2;
16936                   struct «Rowˇ»5.3;
16937                   struct Row5;
16938                   struct Row6;
16939                   ˇ
16940                   struct Row9.1;
16941                   struct «Rowˇ»9.2;
16942                   struct «ˇRow»9.3;
16943                   struct Row8;
16944                   struct Row9;
16945                   «ˇ// something on bottom»
16946                   struct Row10;"#},
16947        vec![
16948            DiffHunkStatusKind::Added,
16949            DiffHunkStatusKind::Added,
16950            DiffHunkStatusKind::Added,
16951            DiffHunkStatusKind::Added,
16952            DiffHunkStatusKind::Added,
16953        ],
16954        indoc! {r#"struct Row;
16955                   ˇstruct Row1;
16956                   struct Row2;
16957                   ˇ
16958                   struct Row4;
16959                   ˇstruct Row5;
16960                   struct Row6;
16961                   ˇ
16962                   ˇstruct Row8;
16963                   struct Row9;
16964                   ˇstruct Row10;"#},
16965        base_text,
16966        &mut cx,
16967    );
16968}
16969
16970#[gpui::test]
16971async fn test_modification_reverts(cx: &mut TestAppContext) {
16972    init_test(cx, |_| {});
16973    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16974    let base_text = indoc! {r#"
16975        struct Row;
16976        struct Row1;
16977        struct Row2;
16978
16979        struct Row4;
16980        struct Row5;
16981        struct Row6;
16982
16983        struct Row8;
16984        struct Row9;
16985        struct Row10;"#};
16986
16987    // Modification hunks behave the same as the addition ones.
16988    assert_hunk_revert(
16989        indoc! {r#"struct Row;
16990                   struct Row1;
16991                   struct Row33;
16992                   ˇ
16993                   struct Row4;
16994                   struct Row5;
16995                   struct Row6;
16996                   ˇ
16997                   struct Row99;
16998                   struct Row9;
16999                   struct Row10;"#},
17000        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
17001        indoc! {r#"struct Row;
17002                   struct Row1;
17003                   struct Row33;
17004                   ˇ
17005                   struct Row4;
17006                   struct Row5;
17007                   struct Row6;
17008                   ˇ
17009                   struct Row99;
17010                   struct Row9;
17011                   struct Row10;"#},
17012        base_text,
17013        &mut cx,
17014    );
17015    assert_hunk_revert(
17016        indoc! {r#"struct Row;
17017                   struct Row1;
17018                   struct Row33;
17019                   «ˇ
17020                   struct Row4;
17021                   struct» Row5;
17022                   «struct Row6;
17023                   ˇ»
17024                   struct Row99;
17025                   struct Row9;
17026                   struct Row10;"#},
17027        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
17028        indoc! {r#"struct Row;
17029                   struct Row1;
17030                   struct Row33;
17031                   «ˇ
17032                   struct Row4;
17033                   struct» Row5;
17034                   «struct Row6;
17035                   ˇ»
17036                   struct Row99;
17037                   struct Row9;
17038                   struct Row10;"#},
17039        base_text,
17040        &mut cx,
17041    );
17042
17043    assert_hunk_revert(
17044        indoc! {r#"ˇstruct Row1.1;
17045                   struct Row1;
17046                   «ˇstr»uct Row22;
17047
17048                   struct ˇRow44;
17049                   struct Row5;
17050                   struct «Rˇ»ow66;ˇ
17051
17052                   «struˇ»ct Row88;
17053                   struct Row9;
17054                   struct Row1011;ˇ"#},
17055        vec![
17056            DiffHunkStatusKind::Modified,
17057            DiffHunkStatusKind::Modified,
17058            DiffHunkStatusKind::Modified,
17059            DiffHunkStatusKind::Modified,
17060            DiffHunkStatusKind::Modified,
17061            DiffHunkStatusKind::Modified,
17062        ],
17063        indoc! {r#"struct Row;
17064                   ˇstruct Row1;
17065                   struct Row2;
17066                   ˇ
17067                   struct Row4;
17068                   ˇstruct Row5;
17069                   struct Row6;
17070                   ˇ
17071                   struct Row8;
17072                   ˇstruct Row9;
17073                   struct Row10;ˇ"#},
17074        base_text,
17075        &mut cx,
17076    );
17077}
17078
17079#[gpui::test]
17080async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
17081    init_test(cx, |_| {});
17082    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
17083    let base_text = indoc! {r#"
17084        one
17085
17086        two
17087        three
17088        "#};
17089
17090    cx.set_head_text(base_text);
17091    cx.set_state("\nˇ\n");
17092    cx.executor().run_until_parked();
17093    cx.update_editor(|editor, _window, cx| {
17094        editor.expand_selected_diff_hunks(cx);
17095    });
17096    cx.executor().run_until_parked();
17097    cx.update_editor(|editor, window, cx| {
17098        editor.backspace(&Default::default(), window, cx);
17099    });
17100    cx.run_until_parked();
17101    cx.assert_state_with_diff(
17102        indoc! {r#"
17103
17104        - two
17105        - threeˇ
17106        +
17107        "#}
17108        .to_string(),
17109    );
17110}
17111
17112#[gpui::test]
17113async fn test_deletion_reverts(cx: &mut TestAppContext) {
17114    init_test(cx, |_| {});
17115    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
17116    let base_text = indoc! {r#"struct Row;
17117struct Row1;
17118struct Row2;
17119
17120struct Row4;
17121struct Row5;
17122struct Row6;
17123
17124struct Row8;
17125struct Row9;
17126struct Row10;"#};
17127
17128    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
17129    assert_hunk_revert(
17130        indoc! {r#"struct Row;
17131                   struct Row2;
17132
17133                   ˇstruct Row4;
17134                   struct Row5;
17135                   struct Row6;
17136                   ˇ
17137                   struct Row8;
17138                   struct Row10;"#},
17139        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
17140        indoc! {r#"struct Row;
17141                   struct Row2;
17142
17143                   ˇstruct Row4;
17144                   struct Row5;
17145                   struct Row6;
17146                   ˇ
17147                   struct Row8;
17148                   struct Row10;"#},
17149        base_text,
17150        &mut cx,
17151    );
17152    assert_hunk_revert(
17153        indoc! {r#"struct Row;
17154                   struct Row2;
17155
17156                   «ˇstruct Row4;
17157                   struct» Row5;
17158                   «struct Row6;
17159                   ˇ»
17160                   struct Row8;
17161                   struct Row10;"#},
17162        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
17163        indoc! {r#"struct Row;
17164                   struct Row2;
17165
17166                   «ˇstruct Row4;
17167                   struct» Row5;
17168                   «struct Row6;
17169                   ˇ»
17170                   struct Row8;
17171                   struct Row10;"#},
17172        base_text,
17173        &mut cx,
17174    );
17175
17176    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
17177    assert_hunk_revert(
17178        indoc! {r#"struct Row;
17179                   ˇstruct Row2;
17180
17181                   struct Row4;
17182                   struct Row5;
17183                   struct Row6;
17184
17185                   struct Row8;ˇ
17186                   struct Row10;"#},
17187        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
17188        indoc! {r#"struct Row;
17189                   struct Row1;
17190                   ˇstruct Row2;
17191
17192                   struct Row4;
17193                   struct Row5;
17194                   struct Row6;
17195
17196                   struct Row8;ˇ
17197                   struct Row9;
17198                   struct Row10;"#},
17199        base_text,
17200        &mut cx,
17201    );
17202    assert_hunk_revert(
17203        indoc! {r#"struct Row;
17204                   struct Row2«ˇ;
17205                   struct Row4;
17206                   struct» Row5;
17207                   «struct Row6;
17208
17209                   struct Row8;ˇ»
17210                   struct Row10;"#},
17211        vec![
17212            DiffHunkStatusKind::Deleted,
17213            DiffHunkStatusKind::Deleted,
17214            DiffHunkStatusKind::Deleted,
17215        ],
17216        indoc! {r#"struct Row;
17217                   struct Row1;
17218                   struct Row2«ˇ;
17219
17220                   struct Row4;
17221                   struct» Row5;
17222                   «struct Row6;
17223
17224                   struct Row8;ˇ»
17225                   struct Row9;
17226                   struct Row10;"#},
17227        base_text,
17228        &mut cx,
17229    );
17230}
17231
17232#[gpui::test]
17233async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
17234    init_test(cx, |_| {});
17235
17236    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
17237    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
17238    let base_text_3 =
17239        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
17240
17241    let text_1 = edit_first_char_of_every_line(base_text_1);
17242    let text_2 = edit_first_char_of_every_line(base_text_2);
17243    let text_3 = edit_first_char_of_every_line(base_text_3);
17244
17245    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
17246    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
17247    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
17248
17249    let multibuffer = cx.new(|cx| {
17250        let mut multibuffer = MultiBuffer::new(ReadWrite);
17251        multibuffer.push_excerpts(
17252            buffer_1.clone(),
17253            [
17254                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17255                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17256                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17257            ],
17258            cx,
17259        );
17260        multibuffer.push_excerpts(
17261            buffer_2.clone(),
17262            [
17263                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17264                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17265                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17266            ],
17267            cx,
17268        );
17269        multibuffer.push_excerpts(
17270            buffer_3.clone(),
17271            [
17272                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17273                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17274                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17275            ],
17276            cx,
17277        );
17278        multibuffer
17279    });
17280
17281    let fs = FakeFs::new(cx.executor());
17282    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
17283    let (editor, cx) = cx
17284        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
17285    editor.update_in(cx, |editor, _window, cx| {
17286        for (buffer, diff_base) in [
17287            (buffer_1.clone(), base_text_1),
17288            (buffer_2.clone(), base_text_2),
17289            (buffer_3.clone(), base_text_3),
17290        ] {
17291            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
17292            editor
17293                .buffer
17294                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
17295        }
17296    });
17297    cx.executor().run_until_parked();
17298
17299    editor.update_in(cx, |editor, window, cx| {
17300        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}");
17301        editor.select_all(&SelectAll, window, cx);
17302        editor.git_restore(&Default::default(), window, cx);
17303    });
17304    cx.executor().run_until_parked();
17305
17306    // When all ranges are selected, all buffer hunks are reverted.
17307    editor.update(cx, |editor, cx| {
17308        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");
17309    });
17310    buffer_1.update(cx, |buffer, _| {
17311        assert_eq!(buffer.text(), base_text_1);
17312    });
17313    buffer_2.update(cx, |buffer, _| {
17314        assert_eq!(buffer.text(), base_text_2);
17315    });
17316    buffer_3.update(cx, |buffer, _| {
17317        assert_eq!(buffer.text(), base_text_3);
17318    });
17319
17320    editor.update_in(cx, |editor, window, cx| {
17321        editor.undo(&Default::default(), window, cx);
17322    });
17323
17324    editor.update_in(cx, |editor, window, cx| {
17325        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17326            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
17327        });
17328        editor.git_restore(&Default::default(), window, cx);
17329    });
17330
17331    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
17332    // but not affect buffer_2 and its related excerpts.
17333    editor.update(cx, |editor, cx| {
17334        assert_eq!(
17335            editor.text(cx),
17336            "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}"
17337        );
17338    });
17339    buffer_1.update(cx, |buffer, _| {
17340        assert_eq!(buffer.text(), base_text_1);
17341    });
17342    buffer_2.update(cx, |buffer, _| {
17343        assert_eq!(
17344            buffer.text(),
17345            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
17346        );
17347    });
17348    buffer_3.update(cx, |buffer, _| {
17349        assert_eq!(
17350            buffer.text(),
17351            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
17352        );
17353    });
17354
17355    fn edit_first_char_of_every_line(text: &str) -> String {
17356        text.split('\n')
17357            .map(|line| format!("X{}", &line[1..]))
17358            .collect::<Vec<_>>()
17359            .join("\n")
17360    }
17361}
17362
17363#[gpui::test]
17364async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
17365    init_test(cx, |_| {});
17366
17367    let cols = 4;
17368    let rows = 10;
17369    let sample_text_1 = sample_text(rows, cols, 'a');
17370    assert_eq!(
17371        sample_text_1,
17372        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
17373    );
17374    let sample_text_2 = sample_text(rows, cols, 'l');
17375    assert_eq!(
17376        sample_text_2,
17377        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
17378    );
17379    let sample_text_3 = sample_text(rows, cols, 'v');
17380    assert_eq!(
17381        sample_text_3,
17382        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
17383    );
17384
17385    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
17386    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
17387    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
17388
17389    let multi_buffer = cx.new(|cx| {
17390        let mut multibuffer = MultiBuffer::new(ReadWrite);
17391        multibuffer.push_excerpts(
17392            buffer_1.clone(),
17393            [
17394                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17395                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17396                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17397            ],
17398            cx,
17399        );
17400        multibuffer.push_excerpts(
17401            buffer_2.clone(),
17402            [
17403                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17404                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17405                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17406            ],
17407            cx,
17408        );
17409        multibuffer.push_excerpts(
17410            buffer_3.clone(),
17411            [
17412                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17413                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17414                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17415            ],
17416            cx,
17417        );
17418        multibuffer
17419    });
17420
17421    let fs = FakeFs::new(cx.executor());
17422    fs.insert_tree(
17423        "/a",
17424        json!({
17425            "main.rs": sample_text_1,
17426            "other.rs": sample_text_2,
17427            "lib.rs": sample_text_3,
17428        }),
17429    )
17430    .await;
17431    let project = Project::test(fs, ["/a".as_ref()], cx).await;
17432    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17433    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17434    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
17435        Editor::new(
17436            EditorMode::full(),
17437            multi_buffer,
17438            Some(project.clone()),
17439            window,
17440            cx,
17441        )
17442    });
17443    let multibuffer_item_id = workspace
17444        .update(cx, |workspace, window, cx| {
17445            assert!(
17446                workspace.active_item(cx).is_none(),
17447                "active item should be None before the first item is added"
17448            );
17449            workspace.add_item_to_active_pane(
17450                Box::new(multi_buffer_editor.clone()),
17451                None,
17452                true,
17453                window,
17454                cx,
17455            );
17456            let active_item = workspace
17457                .active_item(cx)
17458                .expect("should have an active item after adding the multi buffer");
17459            assert!(
17460                !active_item.is_singleton(cx),
17461                "A multi buffer was expected to active after adding"
17462            );
17463            active_item.item_id()
17464        })
17465        .unwrap();
17466    cx.executor().run_until_parked();
17467
17468    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17469        editor.change_selections(
17470            SelectionEffects::scroll(Autoscroll::Next),
17471            window,
17472            cx,
17473            |s| s.select_ranges(Some(1..2)),
17474        );
17475        editor.open_excerpts(&OpenExcerpts, window, cx);
17476    });
17477    cx.executor().run_until_parked();
17478    let first_item_id = workspace
17479        .update(cx, |workspace, window, cx| {
17480            let active_item = workspace
17481                .active_item(cx)
17482                .expect("should have an active item after navigating into the 1st buffer");
17483            let first_item_id = active_item.item_id();
17484            assert_ne!(
17485                first_item_id, multibuffer_item_id,
17486                "Should navigate into the 1st buffer and activate it"
17487            );
17488            assert!(
17489                active_item.is_singleton(cx),
17490                "New active item should be a singleton buffer"
17491            );
17492            assert_eq!(
17493                active_item
17494                    .act_as::<Editor>(cx)
17495                    .expect("should have navigated into an editor for the 1st buffer")
17496                    .read(cx)
17497                    .text(cx),
17498                sample_text_1
17499            );
17500
17501            workspace
17502                .go_back(workspace.active_pane().downgrade(), window, cx)
17503                .detach_and_log_err(cx);
17504
17505            first_item_id
17506        })
17507        .unwrap();
17508    cx.executor().run_until_parked();
17509    workspace
17510        .update(cx, |workspace, _, cx| {
17511            let active_item = workspace
17512                .active_item(cx)
17513                .expect("should have an active item after navigating back");
17514            assert_eq!(
17515                active_item.item_id(),
17516                multibuffer_item_id,
17517                "Should navigate back to the multi buffer"
17518            );
17519            assert!(!active_item.is_singleton(cx));
17520        })
17521        .unwrap();
17522
17523    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17524        editor.change_selections(
17525            SelectionEffects::scroll(Autoscroll::Next),
17526            window,
17527            cx,
17528            |s| s.select_ranges(Some(39..40)),
17529        );
17530        editor.open_excerpts(&OpenExcerpts, window, cx);
17531    });
17532    cx.executor().run_until_parked();
17533    let second_item_id = workspace
17534        .update(cx, |workspace, window, cx| {
17535            let active_item = workspace
17536                .active_item(cx)
17537                .expect("should have an active item after navigating into the 2nd buffer");
17538            let second_item_id = active_item.item_id();
17539            assert_ne!(
17540                second_item_id, multibuffer_item_id,
17541                "Should navigate away from the multibuffer"
17542            );
17543            assert_ne!(
17544                second_item_id, first_item_id,
17545                "Should navigate into the 2nd buffer and activate it"
17546            );
17547            assert!(
17548                active_item.is_singleton(cx),
17549                "New active item should be a singleton buffer"
17550            );
17551            assert_eq!(
17552                active_item
17553                    .act_as::<Editor>(cx)
17554                    .expect("should have navigated into an editor")
17555                    .read(cx)
17556                    .text(cx),
17557                sample_text_2
17558            );
17559
17560            workspace
17561                .go_back(workspace.active_pane().downgrade(), window, cx)
17562                .detach_and_log_err(cx);
17563
17564            second_item_id
17565        })
17566        .unwrap();
17567    cx.executor().run_until_parked();
17568    workspace
17569        .update(cx, |workspace, _, cx| {
17570            let active_item = workspace
17571                .active_item(cx)
17572                .expect("should have an active item after navigating back from the 2nd buffer");
17573            assert_eq!(
17574                active_item.item_id(),
17575                multibuffer_item_id,
17576                "Should navigate back from the 2nd buffer to the multi buffer"
17577            );
17578            assert!(!active_item.is_singleton(cx));
17579        })
17580        .unwrap();
17581
17582    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17583        editor.change_selections(
17584            SelectionEffects::scroll(Autoscroll::Next),
17585            window,
17586            cx,
17587            |s| s.select_ranges(Some(70..70)),
17588        );
17589        editor.open_excerpts(&OpenExcerpts, window, cx);
17590    });
17591    cx.executor().run_until_parked();
17592    workspace
17593        .update(cx, |workspace, window, cx| {
17594            let active_item = workspace
17595                .active_item(cx)
17596                .expect("should have an active item after navigating into the 3rd buffer");
17597            let third_item_id = active_item.item_id();
17598            assert_ne!(
17599                third_item_id, multibuffer_item_id,
17600                "Should navigate into the 3rd buffer and activate it"
17601            );
17602            assert_ne!(third_item_id, first_item_id);
17603            assert_ne!(third_item_id, second_item_id);
17604            assert!(
17605                active_item.is_singleton(cx),
17606                "New active item should be a singleton buffer"
17607            );
17608            assert_eq!(
17609                active_item
17610                    .act_as::<Editor>(cx)
17611                    .expect("should have navigated into an editor")
17612                    .read(cx)
17613                    .text(cx),
17614                sample_text_3
17615            );
17616
17617            workspace
17618                .go_back(workspace.active_pane().downgrade(), window, cx)
17619                .detach_and_log_err(cx);
17620        })
17621        .unwrap();
17622    cx.executor().run_until_parked();
17623    workspace
17624        .update(cx, |workspace, _, cx| {
17625            let active_item = workspace
17626                .active_item(cx)
17627                .expect("should have an active item after navigating back from the 3rd buffer");
17628            assert_eq!(
17629                active_item.item_id(),
17630                multibuffer_item_id,
17631                "Should navigate back from the 3rd buffer to the multi buffer"
17632            );
17633            assert!(!active_item.is_singleton(cx));
17634        })
17635        .unwrap();
17636}
17637
17638#[gpui::test]
17639async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17640    init_test(cx, |_| {});
17641
17642    let mut cx = EditorTestContext::new(cx).await;
17643
17644    let diff_base = r#"
17645        use some::mod;
17646
17647        const A: u32 = 42;
17648
17649        fn main() {
17650            println!("hello");
17651
17652            println!("world");
17653        }
17654        "#
17655    .unindent();
17656
17657    cx.set_state(
17658        &r#"
17659        use some::modified;
17660
17661        ˇ
17662        fn main() {
17663            println!("hello there");
17664
17665            println!("around the");
17666            println!("world");
17667        }
17668        "#
17669        .unindent(),
17670    );
17671
17672    cx.set_head_text(&diff_base);
17673    executor.run_until_parked();
17674
17675    cx.update_editor(|editor, window, cx| {
17676        editor.go_to_next_hunk(&GoToHunk, window, cx);
17677        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17678    });
17679    executor.run_until_parked();
17680    cx.assert_state_with_diff(
17681        r#"
17682          use some::modified;
17683
17684
17685          fn main() {
17686        -     println!("hello");
17687        + ˇ    println!("hello there");
17688
17689              println!("around the");
17690              println!("world");
17691          }
17692        "#
17693        .unindent(),
17694    );
17695
17696    cx.update_editor(|editor, window, cx| {
17697        for _ in 0..2 {
17698            editor.go_to_next_hunk(&GoToHunk, window, cx);
17699            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17700        }
17701    });
17702    executor.run_until_parked();
17703    cx.assert_state_with_diff(
17704        r#"
17705        - use some::mod;
17706        + ˇuse some::modified;
17707
17708
17709          fn main() {
17710        -     println!("hello");
17711        +     println!("hello there");
17712
17713        +     println!("around the");
17714              println!("world");
17715          }
17716        "#
17717        .unindent(),
17718    );
17719
17720    cx.update_editor(|editor, window, cx| {
17721        editor.go_to_next_hunk(&GoToHunk, window, cx);
17722        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17723    });
17724    executor.run_until_parked();
17725    cx.assert_state_with_diff(
17726        r#"
17727        - use some::mod;
17728        + use some::modified;
17729
17730        - const A: u32 = 42;
17731          ˇ
17732          fn main() {
17733        -     println!("hello");
17734        +     println!("hello there");
17735
17736        +     println!("around the");
17737              println!("world");
17738          }
17739        "#
17740        .unindent(),
17741    );
17742
17743    cx.update_editor(|editor, window, cx| {
17744        editor.cancel(&Cancel, window, cx);
17745    });
17746
17747    cx.assert_state_with_diff(
17748        r#"
17749          use some::modified;
17750
17751          ˇ
17752          fn main() {
17753              println!("hello there");
17754
17755              println!("around the");
17756              println!("world");
17757          }
17758        "#
17759        .unindent(),
17760    );
17761}
17762
17763#[gpui::test]
17764async fn test_diff_base_change_with_expanded_diff_hunks(
17765    executor: BackgroundExecutor,
17766    cx: &mut TestAppContext,
17767) {
17768    init_test(cx, |_| {});
17769
17770    let mut cx = EditorTestContext::new(cx).await;
17771
17772    let diff_base = r#"
17773        use some::mod1;
17774        use some::mod2;
17775
17776        const A: u32 = 42;
17777        const B: u32 = 42;
17778        const C: u32 = 42;
17779
17780        fn main() {
17781            println!("hello");
17782
17783            println!("world");
17784        }
17785        "#
17786    .unindent();
17787
17788    cx.set_state(
17789        &r#"
17790        use some::mod2;
17791
17792        const A: u32 = 42;
17793        const C: u32 = 42;
17794
17795        fn main(ˇ) {
17796            //println!("hello");
17797
17798            println!("world");
17799            //
17800            //
17801        }
17802        "#
17803        .unindent(),
17804    );
17805
17806    cx.set_head_text(&diff_base);
17807    executor.run_until_parked();
17808
17809    cx.update_editor(|editor, window, cx| {
17810        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17811    });
17812    executor.run_until_parked();
17813    cx.assert_state_with_diff(
17814        r#"
17815        - use some::mod1;
17816          use some::mod2;
17817
17818          const A: u32 = 42;
17819        - const B: u32 = 42;
17820          const C: u32 = 42;
17821
17822          fn main(ˇ) {
17823        -     println!("hello");
17824        +     //println!("hello");
17825
17826              println!("world");
17827        +     //
17828        +     //
17829          }
17830        "#
17831        .unindent(),
17832    );
17833
17834    cx.set_head_text("new diff base!");
17835    executor.run_until_parked();
17836    cx.assert_state_with_diff(
17837        r#"
17838        - new diff base!
17839        + use some::mod2;
17840        +
17841        + const A: u32 = 42;
17842        + const C: u32 = 42;
17843        +
17844        + fn main(ˇ) {
17845        +     //println!("hello");
17846        +
17847        +     println!("world");
17848        +     //
17849        +     //
17850        + }
17851        "#
17852        .unindent(),
17853    );
17854}
17855
17856#[gpui::test]
17857async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
17858    init_test(cx, |_| {});
17859
17860    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
17861    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
17862    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
17863    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
17864    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
17865    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
17866
17867    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
17868    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
17869    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
17870
17871    let multi_buffer = cx.new(|cx| {
17872        let mut multibuffer = MultiBuffer::new(ReadWrite);
17873        multibuffer.push_excerpts(
17874            buffer_1.clone(),
17875            [
17876                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17877                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17878                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17879            ],
17880            cx,
17881        );
17882        multibuffer.push_excerpts(
17883            buffer_2.clone(),
17884            [
17885                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17886                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17887                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17888            ],
17889            cx,
17890        );
17891        multibuffer.push_excerpts(
17892            buffer_3.clone(),
17893            [
17894                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17895                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17896                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17897            ],
17898            cx,
17899        );
17900        multibuffer
17901    });
17902
17903    let editor =
17904        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
17905    editor
17906        .update(cx, |editor, _window, cx| {
17907            for (buffer, diff_base) in [
17908                (buffer_1.clone(), file_1_old),
17909                (buffer_2.clone(), file_2_old),
17910                (buffer_3.clone(), file_3_old),
17911            ] {
17912                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
17913                editor
17914                    .buffer
17915                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
17916            }
17917        })
17918        .unwrap();
17919
17920    let mut cx = EditorTestContext::for_editor(editor, cx).await;
17921    cx.run_until_parked();
17922
17923    cx.assert_editor_state(
17924        &"
17925            ˇaaa
17926            ccc
17927            ddd
17928
17929            ggg
17930            hhh
17931
17932
17933            lll
17934            mmm
17935            NNN
17936
17937            qqq
17938            rrr
17939
17940            uuu
17941            111
17942            222
17943            333
17944
17945            666
17946            777
17947
17948            000
17949            !!!"
17950        .unindent(),
17951    );
17952
17953    cx.update_editor(|editor, window, cx| {
17954        editor.select_all(&SelectAll, window, cx);
17955        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17956    });
17957    cx.executor().run_until_parked();
17958
17959    cx.assert_state_with_diff(
17960        "
17961            «aaa
17962          - bbb
17963            ccc
17964            ddd
17965
17966            ggg
17967            hhh
17968
17969
17970            lll
17971            mmm
17972          - nnn
17973          + NNN
17974
17975            qqq
17976            rrr
17977
17978            uuu
17979            111
17980            222
17981            333
17982
17983          + 666
17984            777
17985
17986            000
17987            !!!ˇ»"
17988            .unindent(),
17989    );
17990}
17991
17992#[gpui::test]
17993async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
17994    init_test(cx, |_| {});
17995
17996    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
17997    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
17998
17999    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
18000    let multi_buffer = cx.new(|cx| {
18001        let mut multibuffer = MultiBuffer::new(ReadWrite);
18002        multibuffer.push_excerpts(
18003            buffer.clone(),
18004            [
18005                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
18006                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
18007                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
18008            ],
18009            cx,
18010        );
18011        multibuffer
18012    });
18013
18014    let editor =
18015        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
18016    editor
18017        .update(cx, |editor, _window, cx| {
18018            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
18019            editor
18020                .buffer
18021                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
18022        })
18023        .unwrap();
18024
18025    let mut cx = EditorTestContext::for_editor(editor, cx).await;
18026    cx.run_until_parked();
18027
18028    cx.update_editor(|editor, window, cx| {
18029        editor.expand_all_diff_hunks(&Default::default(), window, cx)
18030    });
18031    cx.executor().run_until_parked();
18032
18033    // When the start of a hunk coincides with the start of its excerpt,
18034    // the hunk is expanded. When the start of a a hunk is earlier than
18035    // the start of its excerpt, the hunk is not expanded.
18036    cx.assert_state_with_diff(
18037        "
18038            ˇaaa
18039          - bbb
18040          + BBB
18041
18042          - ddd
18043          - eee
18044          + DDD
18045          + EEE
18046            fff
18047
18048            iii
18049        "
18050        .unindent(),
18051    );
18052}
18053
18054#[gpui::test]
18055async fn test_edits_around_expanded_insertion_hunks(
18056    executor: BackgroundExecutor,
18057    cx: &mut TestAppContext,
18058) {
18059    init_test(cx, |_| {});
18060
18061    let mut cx = EditorTestContext::new(cx).await;
18062
18063    let diff_base = r#"
18064        use some::mod1;
18065        use some::mod2;
18066
18067        const A: u32 = 42;
18068
18069        fn main() {
18070            println!("hello");
18071
18072            println!("world");
18073        }
18074        "#
18075    .unindent();
18076    executor.run_until_parked();
18077    cx.set_state(
18078        &r#"
18079        use some::mod1;
18080        use some::mod2;
18081
18082        const A: u32 = 42;
18083        const B: u32 = 42;
18084        const C: u32 = 42;
18085        ˇ
18086
18087        fn main() {
18088            println!("hello");
18089
18090            println!("world");
18091        }
18092        "#
18093        .unindent(),
18094    );
18095
18096    cx.set_head_text(&diff_base);
18097    executor.run_until_parked();
18098
18099    cx.update_editor(|editor, window, cx| {
18100        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18101    });
18102    executor.run_until_parked();
18103
18104    cx.assert_state_with_diff(
18105        r#"
18106        use some::mod1;
18107        use some::mod2;
18108
18109        const A: u32 = 42;
18110      + const B: u32 = 42;
18111      + const C: u32 = 42;
18112      + ˇ
18113
18114        fn main() {
18115            println!("hello");
18116
18117            println!("world");
18118        }
18119      "#
18120        .unindent(),
18121    );
18122
18123    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
18124    executor.run_until_parked();
18125
18126    cx.assert_state_with_diff(
18127        r#"
18128        use some::mod1;
18129        use some::mod2;
18130
18131        const A: u32 = 42;
18132      + const B: u32 = 42;
18133      + const C: u32 = 42;
18134      + const D: u32 = 42;
18135      + ˇ
18136
18137        fn main() {
18138            println!("hello");
18139
18140            println!("world");
18141        }
18142      "#
18143        .unindent(),
18144    );
18145
18146    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
18147    executor.run_until_parked();
18148
18149    cx.assert_state_with_diff(
18150        r#"
18151        use some::mod1;
18152        use some::mod2;
18153
18154        const A: u32 = 42;
18155      + const B: u32 = 42;
18156      + const C: u32 = 42;
18157      + const D: u32 = 42;
18158      + const E: u32 = 42;
18159      + ˇ
18160
18161        fn main() {
18162            println!("hello");
18163
18164            println!("world");
18165        }
18166      "#
18167        .unindent(),
18168    );
18169
18170    cx.update_editor(|editor, window, cx| {
18171        editor.delete_line(&DeleteLine, window, cx);
18172    });
18173    executor.run_until_parked();
18174
18175    cx.assert_state_with_diff(
18176        r#"
18177        use some::mod1;
18178        use some::mod2;
18179
18180        const A: u32 = 42;
18181      + const B: u32 = 42;
18182      + const C: u32 = 42;
18183      + const D: u32 = 42;
18184      + const E: u32 = 42;
18185        ˇ
18186        fn main() {
18187            println!("hello");
18188
18189            println!("world");
18190        }
18191      "#
18192        .unindent(),
18193    );
18194
18195    cx.update_editor(|editor, window, cx| {
18196        editor.move_up(&MoveUp, window, cx);
18197        editor.delete_line(&DeleteLine, window, cx);
18198        editor.move_up(&MoveUp, window, cx);
18199        editor.delete_line(&DeleteLine, window, cx);
18200        editor.move_up(&MoveUp, window, cx);
18201        editor.delete_line(&DeleteLine, window, cx);
18202    });
18203    executor.run_until_parked();
18204    cx.assert_state_with_diff(
18205        r#"
18206        use some::mod1;
18207        use some::mod2;
18208
18209        const A: u32 = 42;
18210      + const B: u32 = 42;
18211        ˇ
18212        fn main() {
18213            println!("hello");
18214
18215            println!("world");
18216        }
18217      "#
18218        .unindent(),
18219    );
18220
18221    cx.update_editor(|editor, window, cx| {
18222        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
18223        editor.delete_line(&DeleteLine, window, cx);
18224    });
18225    executor.run_until_parked();
18226    cx.assert_state_with_diff(
18227        r#"
18228        ˇ
18229        fn main() {
18230            println!("hello");
18231
18232            println!("world");
18233        }
18234      "#
18235        .unindent(),
18236    );
18237}
18238
18239#[gpui::test]
18240async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
18241    init_test(cx, |_| {});
18242
18243    let mut cx = EditorTestContext::new(cx).await;
18244    cx.set_head_text(indoc! { "
18245        one
18246        two
18247        three
18248        four
18249        five
18250        "
18251    });
18252    cx.set_state(indoc! { "
18253        one
18254        ˇthree
18255        five
18256    "});
18257    cx.run_until_parked();
18258    cx.update_editor(|editor, window, cx| {
18259        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
18260    });
18261    cx.assert_state_with_diff(
18262        indoc! { "
18263        one
18264      - two
18265        ˇthree
18266      - four
18267        five
18268    "}
18269        .to_string(),
18270    );
18271    cx.update_editor(|editor, window, cx| {
18272        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
18273    });
18274
18275    cx.assert_state_with_diff(
18276        indoc! { "
18277        one
18278        ˇthree
18279        five
18280    "}
18281        .to_string(),
18282    );
18283
18284    cx.set_state(indoc! { "
18285        one
18286        ˇTWO
18287        three
18288        four
18289        five
18290    "});
18291    cx.run_until_parked();
18292    cx.update_editor(|editor, window, cx| {
18293        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
18294    });
18295
18296    cx.assert_state_with_diff(
18297        indoc! { "
18298            one
18299          - two
18300          + ˇTWO
18301            three
18302            four
18303            five
18304        "}
18305        .to_string(),
18306    );
18307    cx.update_editor(|editor, window, cx| {
18308        editor.move_up(&Default::default(), window, cx);
18309        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
18310    });
18311    cx.assert_state_with_diff(
18312        indoc! { "
18313            one
18314            ˇTWO
18315            three
18316            four
18317            five
18318        "}
18319        .to_string(),
18320    );
18321}
18322
18323#[gpui::test]
18324async fn test_edits_around_expanded_deletion_hunks(
18325    executor: BackgroundExecutor,
18326    cx: &mut TestAppContext,
18327) {
18328    init_test(cx, |_| {});
18329
18330    let mut cx = EditorTestContext::new(cx).await;
18331
18332    let diff_base = r#"
18333        use some::mod1;
18334        use some::mod2;
18335
18336        const A: u32 = 42;
18337        const B: u32 = 42;
18338        const C: u32 = 42;
18339
18340
18341        fn main() {
18342            println!("hello");
18343
18344            println!("world");
18345        }
18346    "#
18347    .unindent();
18348    executor.run_until_parked();
18349    cx.set_state(
18350        &r#"
18351        use some::mod1;
18352        use some::mod2;
18353
18354        ˇconst B: u32 = 42;
18355        const C: u32 = 42;
18356
18357
18358        fn main() {
18359            println!("hello");
18360
18361            println!("world");
18362        }
18363        "#
18364        .unindent(),
18365    );
18366
18367    cx.set_head_text(&diff_base);
18368    executor.run_until_parked();
18369
18370    cx.update_editor(|editor, window, cx| {
18371        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18372    });
18373    executor.run_until_parked();
18374
18375    cx.assert_state_with_diff(
18376        r#"
18377        use some::mod1;
18378        use some::mod2;
18379
18380      - const A: u32 = 42;
18381        ˇconst B: u32 = 42;
18382        const C: u32 = 42;
18383
18384
18385        fn main() {
18386            println!("hello");
18387
18388            println!("world");
18389        }
18390      "#
18391        .unindent(),
18392    );
18393
18394    cx.update_editor(|editor, window, cx| {
18395        editor.delete_line(&DeleteLine, window, cx);
18396    });
18397    executor.run_until_parked();
18398    cx.assert_state_with_diff(
18399        r#"
18400        use some::mod1;
18401        use some::mod2;
18402
18403      - const A: u32 = 42;
18404      - const B: u32 = 42;
18405        ˇconst C: u32 = 42;
18406
18407
18408        fn main() {
18409            println!("hello");
18410
18411            println!("world");
18412        }
18413      "#
18414        .unindent(),
18415    );
18416
18417    cx.update_editor(|editor, window, cx| {
18418        editor.delete_line(&DeleteLine, window, cx);
18419    });
18420    executor.run_until_parked();
18421    cx.assert_state_with_diff(
18422        r#"
18423        use some::mod1;
18424        use some::mod2;
18425
18426      - const A: u32 = 42;
18427      - const B: u32 = 42;
18428      - const C: u32 = 42;
18429        ˇ
18430
18431        fn main() {
18432            println!("hello");
18433
18434            println!("world");
18435        }
18436      "#
18437        .unindent(),
18438    );
18439
18440    cx.update_editor(|editor, window, cx| {
18441        editor.handle_input("replacement", window, cx);
18442    });
18443    executor.run_until_parked();
18444    cx.assert_state_with_diff(
18445        r#"
18446        use some::mod1;
18447        use some::mod2;
18448
18449      - const A: u32 = 42;
18450      - const B: u32 = 42;
18451      - const C: u32 = 42;
18452      -
18453      + replacementˇ
18454
18455        fn main() {
18456            println!("hello");
18457
18458            println!("world");
18459        }
18460      "#
18461        .unindent(),
18462    );
18463}
18464
18465#[gpui::test]
18466async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18467    init_test(cx, |_| {});
18468
18469    let mut cx = EditorTestContext::new(cx).await;
18470
18471    let base_text = r#"
18472        one
18473        two
18474        three
18475        four
18476        five
18477    "#
18478    .unindent();
18479    executor.run_until_parked();
18480    cx.set_state(
18481        &r#"
18482        one
18483        two
18484        fˇour
18485        five
18486        "#
18487        .unindent(),
18488    );
18489
18490    cx.set_head_text(&base_text);
18491    executor.run_until_parked();
18492
18493    cx.update_editor(|editor, window, cx| {
18494        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18495    });
18496    executor.run_until_parked();
18497
18498    cx.assert_state_with_diff(
18499        r#"
18500          one
18501          two
18502        - three
18503          fˇour
18504          five
18505        "#
18506        .unindent(),
18507    );
18508
18509    cx.update_editor(|editor, window, cx| {
18510        editor.backspace(&Backspace, window, cx);
18511        editor.backspace(&Backspace, window, cx);
18512    });
18513    executor.run_until_parked();
18514    cx.assert_state_with_diff(
18515        r#"
18516          one
18517          two
18518        - threeˇ
18519        - four
18520        + our
18521          five
18522        "#
18523        .unindent(),
18524    );
18525}
18526
18527#[gpui::test]
18528async fn test_edit_after_expanded_modification_hunk(
18529    executor: BackgroundExecutor,
18530    cx: &mut TestAppContext,
18531) {
18532    init_test(cx, |_| {});
18533
18534    let mut cx = EditorTestContext::new(cx).await;
18535
18536    let diff_base = r#"
18537        use some::mod1;
18538        use some::mod2;
18539
18540        const A: u32 = 42;
18541        const B: u32 = 42;
18542        const C: u32 = 42;
18543        const D: u32 = 42;
18544
18545
18546        fn main() {
18547            println!("hello");
18548
18549            println!("world");
18550        }"#
18551    .unindent();
18552
18553    cx.set_state(
18554        &r#"
18555        use some::mod1;
18556        use some::mod2;
18557
18558        const A: u32 = 42;
18559        const B: u32 = 42;
18560        const C: u32 = 43ˇ
18561        const D: u32 = 42;
18562
18563
18564        fn main() {
18565            println!("hello");
18566
18567            println!("world");
18568        }"#
18569        .unindent(),
18570    );
18571
18572    cx.set_head_text(&diff_base);
18573    executor.run_until_parked();
18574    cx.update_editor(|editor, window, cx| {
18575        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18576    });
18577    executor.run_until_parked();
18578
18579    cx.assert_state_with_diff(
18580        r#"
18581        use some::mod1;
18582        use some::mod2;
18583
18584        const A: u32 = 42;
18585        const B: u32 = 42;
18586      - const C: u32 = 42;
18587      + const C: u32 = 43ˇ
18588        const D: u32 = 42;
18589
18590
18591        fn main() {
18592            println!("hello");
18593
18594            println!("world");
18595        }"#
18596        .unindent(),
18597    );
18598
18599    cx.update_editor(|editor, window, cx| {
18600        editor.handle_input("\nnew_line\n", window, cx);
18601    });
18602    executor.run_until_parked();
18603
18604    cx.assert_state_with_diff(
18605        r#"
18606        use some::mod1;
18607        use some::mod2;
18608
18609        const A: u32 = 42;
18610        const B: u32 = 42;
18611      - const C: u32 = 42;
18612      + const C: u32 = 43
18613      + new_line
18614      + ˇ
18615        const D: u32 = 42;
18616
18617
18618        fn main() {
18619            println!("hello");
18620
18621            println!("world");
18622        }"#
18623        .unindent(),
18624    );
18625}
18626
18627#[gpui::test]
18628async fn test_stage_and_unstage_added_file_hunk(
18629    executor: BackgroundExecutor,
18630    cx: &mut TestAppContext,
18631) {
18632    init_test(cx, |_| {});
18633
18634    let mut cx = EditorTestContext::new(cx).await;
18635    cx.update_editor(|editor, _, cx| {
18636        editor.set_expand_all_diff_hunks(cx);
18637    });
18638
18639    let working_copy = r#"
18640            ˇfn main() {
18641                println!("hello, world!");
18642            }
18643        "#
18644    .unindent();
18645
18646    cx.set_state(&working_copy);
18647    executor.run_until_parked();
18648
18649    cx.assert_state_with_diff(
18650        r#"
18651            + ˇfn main() {
18652            +     println!("hello, world!");
18653            + }
18654        "#
18655        .unindent(),
18656    );
18657    cx.assert_index_text(None);
18658
18659    cx.update_editor(|editor, window, cx| {
18660        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18661    });
18662    executor.run_until_parked();
18663    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
18664    cx.assert_state_with_diff(
18665        r#"
18666            + ˇfn main() {
18667            +     println!("hello, world!");
18668            + }
18669        "#
18670        .unindent(),
18671    );
18672
18673    cx.update_editor(|editor, window, cx| {
18674        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18675    });
18676    executor.run_until_parked();
18677    cx.assert_index_text(None);
18678}
18679
18680async fn setup_indent_guides_editor(
18681    text: &str,
18682    cx: &mut TestAppContext,
18683) -> (BufferId, EditorTestContext) {
18684    init_test(cx, |_| {});
18685
18686    let mut cx = EditorTestContext::new(cx).await;
18687
18688    let buffer_id = cx.update_editor(|editor, window, cx| {
18689        editor.set_text(text, window, cx);
18690        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
18691
18692        buffer_ids[0]
18693    });
18694
18695    (buffer_id, cx)
18696}
18697
18698fn assert_indent_guides(
18699    range: Range<u32>,
18700    expected: Vec<IndentGuide>,
18701    active_indices: Option<Vec<usize>>,
18702    cx: &mut EditorTestContext,
18703) {
18704    let indent_guides = cx.update_editor(|editor, window, cx| {
18705        let snapshot = editor.snapshot(window, cx).display_snapshot;
18706        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
18707            editor,
18708            MultiBufferRow(range.start)..MultiBufferRow(range.end),
18709            true,
18710            &snapshot,
18711            cx,
18712        );
18713
18714        indent_guides.sort_by(|a, b| {
18715            a.depth.cmp(&b.depth).then(
18716                a.start_row
18717                    .cmp(&b.start_row)
18718                    .then(a.end_row.cmp(&b.end_row)),
18719            )
18720        });
18721        indent_guides
18722    });
18723
18724    if let Some(expected) = active_indices {
18725        let active_indices = cx.update_editor(|editor, window, cx| {
18726            let snapshot = editor.snapshot(window, cx).display_snapshot;
18727            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
18728        });
18729
18730        assert_eq!(
18731            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
18732            expected,
18733            "Active indent guide indices do not match"
18734        );
18735    }
18736
18737    assert_eq!(indent_guides, expected, "Indent guides do not match");
18738}
18739
18740fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
18741    IndentGuide {
18742        buffer_id,
18743        start_row: MultiBufferRow(start_row),
18744        end_row: MultiBufferRow(end_row),
18745        depth,
18746        tab_size: 4,
18747        settings: IndentGuideSettings {
18748            enabled: true,
18749            line_width: 1,
18750            active_line_width: 1,
18751            ..Default::default()
18752        },
18753    }
18754}
18755
18756#[gpui::test]
18757async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
18758    let (buffer_id, mut cx) = setup_indent_guides_editor(
18759        &"
18760        fn main() {
18761            let a = 1;
18762        }"
18763        .unindent(),
18764        cx,
18765    )
18766    .await;
18767
18768    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
18769}
18770
18771#[gpui::test]
18772async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
18773    let (buffer_id, mut cx) = setup_indent_guides_editor(
18774        &"
18775        fn main() {
18776            let a = 1;
18777            let b = 2;
18778        }"
18779        .unindent(),
18780        cx,
18781    )
18782    .await;
18783
18784    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
18785}
18786
18787#[gpui::test]
18788async fn test_indent_guide_nested(cx: &mut TestAppContext) {
18789    let (buffer_id, mut cx) = setup_indent_guides_editor(
18790        &"
18791        fn main() {
18792            let a = 1;
18793            if a == 3 {
18794                let b = 2;
18795            } else {
18796                let c = 3;
18797            }
18798        }"
18799        .unindent(),
18800        cx,
18801    )
18802    .await;
18803
18804    assert_indent_guides(
18805        0..8,
18806        vec![
18807            indent_guide(buffer_id, 1, 6, 0),
18808            indent_guide(buffer_id, 3, 3, 1),
18809            indent_guide(buffer_id, 5, 5, 1),
18810        ],
18811        None,
18812        &mut cx,
18813    );
18814}
18815
18816#[gpui::test]
18817async fn test_indent_guide_tab(cx: &mut TestAppContext) {
18818    let (buffer_id, mut cx) = setup_indent_guides_editor(
18819        &"
18820        fn main() {
18821            let a = 1;
18822                let b = 2;
18823            let c = 3;
18824        }"
18825        .unindent(),
18826        cx,
18827    )
18828    .await;
18829
18830    assert_indent_guides(
18831        0..5,
18832        vec![
18833            indent_guide(buffer_id, 1, 3, 0),
18834            indent_guide(buffer_id, 2, 2, 1),
18835        ],
18836        None,
18837        &mut cx,
18838    );
18839}
18840
18841#[gpui::test]
18842async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
18843    let (buffer_id, mut cx) = setup_indent_guides_editor(
18844        &"
18845        fn main() {
18846            let a = 1;
18847
18848            let c = 3;
18849        }"
18850        .unindent(),
18851        cx,
18852    )
18853    .await;
18854
18855    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
18856}
18857
18858#[gpui::test]
18859async fn test_indent_guide_complex(cx: &mut TestAppContext) {
18860    let (buffer_id, mut cx) = setup_indent_guides_editor(
18861        &"
18862        fn main() {
18863            let a = 1;
18864
18865            let c = 3;
18866
18867            if a == 3 {
18868                let b = 2;
18869            } else {
18870                let c = 3;
18871            }
18872        }"
18873        .unindent(),
18874        cx,
18875    )
18876    .await;
18877
18878    assert_indent_guides(
18879        0..11,
18880        vec![
18881            indent_guide(buffer_id, 1, 9, 0),
18882            indent_guide(buffer_id, 6, 6, 1),
18883            indent_guide(buffer_id, 8, 8, 1),
18884        ],
18885        None,
18886        &mut cx,
18887    );
18888}
18889
18890#[gpui::test]
18891async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
18892    let (buffer_id, mut cx) = setup_indent_guides_editor(
18893        &"
18894        fn main() {
18895            let a = 1;
18896
18897            let c = 3;
18898
18899            if a == 3 {
18900                let b = 2;
18901            } else {
18902                let c = 3;
18903            }
18904        }"
18905        .unindent(),
18906        cx,
18907    )
18908    .await;
18909
18910    assert_indent_guides(
18911        1..11,
18912        vec![
18913            indent_guide(buffer_id, 1, 9, 0),
18914            indent_guide(buffer_id, 6, 6, 1),
18915            indent_guide(buffer_id, 8, 8, 1),
18916        ],
18917        None,
18918        &mut cx,
18919    );
18920}
18921
18922#[gpui::test]
18923async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
18924    let (buffer_id, mut cx) = setup_indent_guides_editor(
18925        &"
18926        fn main() {
18927            let a = 1;
18928
18929            let c = 3;
18930
18931            if a == 3 {
18932                let b = 2;
18933            } else {
18934                let c = 3;
18935            }
18936        }"
18937        .unindent(),
18938        cx,
18939    )
18940    .await;
18941
18942    assert_indent_guides(
18943        1..10,
18944        vec![
18945            indent_guide(buffer_id, 1, 9, 0),
18946            indent_guide(buffer_id, 6, 6, 1),
18947            indent_guide(buffer_id, 8, 8, 1),
18948        ],
18949        None,
18950        &mut cx,
18951    );
18952}
18953
18954#[gpui::test]
18955async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
18956    let (buffer_id, mut cx) = setup_indent_guides_editor(
18957        &"
18958        fn main() {
18959            if a {
18960                b(
18961                    c,
18962                    d,
18963                )
18964            } else {
18965                e(
18966                    f
18967                )
18968            }
18969        }"
18970        .unindent(),
18971        cx,
18972    )
18973    .await;
18974
18975    assert_indent_guides(
18976        0..11,
18977        vec![
18978            indent_guide(buffer_id, 1, 10, 0),
18979            indent_guide(buffer_id, 2, 5, 1),
18980            indent_guide(buffer_id, 7, 9, 1),
18981            indent_guide(buffer_id, 3, 4, 2),
18982            indent_guide(buffer_id, 8, 8, 2),
18983        ],
18984        None,
18985        &mut cx,
18986    );
18987
18988    cx.update_editor(|editor, window, cx| {
18989        editor.fold_at(MultiBufferRow(2), window, cx);
18990        assert_eq!(
18991            editor.display_text(cx),
18992            "
18993            fn main() {
18994                if a {
18995                    b(⋯
18996                    )
18997                } else {
18998                    e(
18999                        f
19000                    )
19001                }
19002            }"
19003            .unindent()
19004        );
19005    });
19006
19007    assert_indent_guides(
19008        0..11,
19009        vec![
19010            indent_guide(buffer_id, 1, 10, 0),
19011            indent_guide(buffer_id, 2, 5, 1),
19012            indent_guide(buffer_id, 7, 9, 1),
19013            indent_guide(buffer_id, 8, 8, 2),
19014        ],
19015        None,
19016        &mut cx,
19017    );
19018}
19019
19020#[gpui::test]
19021async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
19022    let (buffer_id, mut cx) = setup_indent_guides_editor(
19023        &"
19024        block1
19025            block2
19026                block3
19027                    block4
19028            block2
19029        block1
19030        block1"
19031            .unindent(),
19032        cx,
19033    )
19034    .await;
19035
19036    assert_indent_guides(
19037        1..10,
19038        vec![
19039            indent_guide(buffer_id, 1, 4, 0),
19040            indent_guide(buffer_id, 2, 3, 1),
19041            indent_guide(buffer_id, 3, 3, 2),
19042        ],
19043        None,
19044        &mut cx,
19045    );
19046}
19047
19048#[gpui::test]
19049async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
19050    let (buffer_id, mut cx) = setup_indent_guides_editor(
19051        &"
19052        block1
19053            block2
19054                block3
19055
19056        block1
19057        block1"
19058            .unindent(),
19059        cx,
19060    )
19061    .await;
19062
19063    assert_indent_guides(
19064        0..6,
19065        vec![
19066            indent_guide(buffer_id, 1, 2, 0),
19067            indent_guide(buffer_id, 2, 2, 1),
19068        ],
19069        None,
19070        &mut cx,
19071    );
19072}
19073
19074#[gpui::test]
19075async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
19076    let (buffer_id, mut cx) = setup_indent_guides_editor(
19077        &"
19078        function component() {
19079        \treturn (
19080        \t\t\t
19081        \t\t<div>
19082        \t\t\t<abc></abc>
19083        \t\t</div>
19084        \t)
19085        }"
19086        .unindent(),
19087        cx,
19088    )
19089    .await;
19090
19091    assert_indent_guides(
19092        0..8,
19093        vec![
19094            indent_guide(buffer_id, 1, 6, 0),
19095            indent_guide(buffer_id, 2, 5, 1),
19096            indent_guide(buffer_id, 4, 4, 2),
19097        ],
19098        None,
19099        &mut cx,
19100    );
19101}
19102
19103#[gpui::test]
19104async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
19105    let (buffer_id, mut cx) = setup_indent_guides_editor(
19106        &"
19107        function component() {
19108        \treturn (
19109        \t
19110        \t\t<div>
19111        \t\t\t<abc></abc>
19112        \t\t</div>
19113        \t)
19114        }"
19115        .unindent(),
19116        cx,
19117    )
19118    .await;
19119
19120    assert_indent_guides(
19121        0..8,
19122        vec![
19123            indent_guide(buffer_id, 1, 6, 0),
19124            indent_guide(buffer_id, 2, 5, 1),
19125            indent_guide(buffer_id, 4, 4, 2),
19126        ],
19127        None,
19128        &mut cx,
19129    );
19130}
19131
19132#[gpui::test]
19133async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
19134    let (buffer_id, mut cx) = setup_indent_guides_editor(
19135        &"
19136        block1
19137
19138
19139
19140            block2
19141        "
19142        .unindent(),
19143        cx,
19144    )
19145    .await;
19146
19147    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
19148}
19149
19150#[gpui::test]
19151async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
19152    let (buffer_id, mut cx) = setup_indent_guides_editor(
19153        &"
19154        def a:
19155        \tb = 3
19156        \tif True:
19157        \t\tc = 4
19158        \t\td = 5
19159        \tprint(b)
19160        "
19161        .unindent(),
19162        cx,
19163    )
19164    .await;
19165
19166    assert_indent_guides(
19167        0..6,
19168        vec![
19169            indent_guide(buffer_id, 1, 5, 0),
19170            indent_guide(buffer_id, 3, 4, 1),
19171        ],
19172        None,
19173        &mut cx,
19174    );
19175}
19176
19177#[gpui::test]
19178async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
19179    let (buffer_id, mut cx) = setup_indent_guides_editor(
19180        &"
19181    fn main() {
19182        let a = 1;
19183    }"
19184        .unindent(),
19185        cx,
19186    )
19187    .await;
19188
19189    cx.update_editor(|editor, window, cx| {
19190        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19191            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
19192        });
19193    });
19194
19195    assert_indent_guides(
19196        0..3,
19197        vec![indent_guide(buffer_id, 1, 1, 0)],
19198        Some(vec![0]),
19199        &mut cx,
19200    );
19201}
19202
19203#[gpui::test]
19204async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
19205    let (buffer_id, mut cx) = setup_indent_guides_editor(
19206        &"
19207    fn main() {
19208        if 1 == 2 {
19209            let a = 1;
19210        }
19211    }"
19212        .unindent(),
19213        cx,
19214    )
19215    .await;
19216
19217    cx.update_editor(|editor, window, cx| {
19218        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19219            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
19220        });
19221    });
19222
19223    assert_indent_guides(
19224        0..4,
19225        vec![
19226            indent_guide(buffer_id, 1, 3, 0),
19227            indent_guide(buffer_id, 2, 2, 1),
19228        ],
19229        Some(vec![1]),
19230        &mut cx,
19231    );
19232
19233    cx.update_editor(|editor, window, cx| {
19234        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19235            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
19236        });
19237    });
19238
19239    assert_indent_guides(
19240        0..4,
19241        vec![
19242            indent_guide(buffer_id, 1, 3, 0),
19243            indent_guide(buffer_id, 2, 2, 1),
19244        ],
19245        Some(vec![1]),
19246        &mut cx,
19247    );
19248
19249    cx.update_editor(|editor, window, cx| {
19250        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19251            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
19252        });
19253    });
19254
19255    assert_indent_guides(
19256        0..4,
19257        vec![
19258            indent_guide(buffer_id, 1, 3, 0),
19259            indent_guide(buffer_id, 2, 2, 1),
19260        ],
19261        Some(vec![0]),
19262        &mut cx,
19263    );
19264}
19265
19266#[gpui::test]
19267async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
19268    let (buffer_id, mut cx) = setup_indent_guides_editor(
19269        &"
19270    fn main() {
19271        let a = 1;
19272
19273        let b = 2;
19274    }"
19275        .unindent(),
19276        cx,
19277    )
19278    .await;
19279
19280    cx.update_editor(|editor, window, cx| {
19281        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19282            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
19283        });
19284    });
19285
19286    assert_indent_guides(
19287        0..5,
19288        vec![indent_guide(buffer_id, 1, 3, 0)],
19289        Some(vec![0]),
19290        &mut cx,
19291    );
19292}
19293
19294#[gpui::test]
19295async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
19296    let (buffer_id, mut cx) = setup_indent_guides_editor(
19297        &"
19298    def m:
19299        a = 1
19300        pass"
19301            .unindent(),
19302        cx,
19303    )
19304    .await;
19305
19306    cx.update_editor(|editor, window, cx| {
19307        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19308            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
19309        });
19310    });
19311
19312    assert_indent_guides(
19313        0..3,
19314        vec![indent_guide(buffer_id, 1, 2, 0)],
19315        Some(vec![0]),
19316        &mut cx,
19317    );
19318}
19319
19320#[gpui::test]
19321async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
19322    init_test(cx, |_| {});
19323    let mut cx = EditorTestContext::new(cx).await;
19324    let text = indoc! {
19325        "
19326        impl A {
19327            fn b() {
19328                0;
19329                3;
19330                5;
19331                6;
19332                7;
19333            }
19334        }
19335        "
19336    };
19337    let base_text = indoc! {
19338        "
19339        impl A {
19340            fn b() {
19341                0;
19342                1;
19343                2;
19344                3;
19345                4;
19346            }
19347            fn c() {
19348                5;
19349                6;
19350                7;
19351            }
19352        }
19353        "
19354    };
19355
19356    cx.update_editor(|editor, window, cx| {
19357        editor.set_text(text, window, cx);
19358
19359        editor.buffer().update(cx, |multibuffer, cx| {
19360            let buffer = multibuffer.as_singleton().unwrap();
19361            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
19362
19363            multibuffer.set_all_diff_hunks_expanded(cx);
19364            multibuffer.add_diff(diff, cx);
19365
19366            buffer.read(cx).remote_id()
19367        })
19368    });
19369    cx.run_until_parked();
19370
19371    cx.assert_state_with_diff(
19372        indoc! { "
19373          impl A {
19374              fn b() {
19375                  0;
19376        -         1;
19377        -         2;
19378                  3;
19379        -         4;
19380        -     }
19381        -     fn c() {
19382                  5;
19383                  6;
19384                  7;
19385              }
19386          }
19387          ˇ"
19388        }
19389        .to_string(),
19390    );
19391
19392    let mut actual_guides = cx.update_editor(|editor, window, cx| {
19393        editor
19394            .snapshot(window, cx)
19395            .buffer_snapshot
19396            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
19397            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
19398            .collect::<Vec<_>>()
19399    });
19400    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
19401    assert_eq!(
19402        actual_guides,
19403        vec![
19404            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
19405            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
19406            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
19407        ]
19408    );
19409}
19410
19411#[gpui::test]
19412async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19413    init_test(cx, |_| {});
19414    let mut cx = EditorTestContext::new(cx).await;
19415
19416    let diff_base = r#"
19417        a
19418        b
19419        c
19420        "#
19421    .unindent();
19422
19423    cx.set_state(
19424        &r#"
19425        ˇA
19426        b
19427        C
19428        "#
19429        .unindent(),
19430    );
19431    cx.set_head_text(&diff_base);
19432    cx.update_editor(|editor, window, cx| {
19433        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19434    });
19435    executor.run_until_parked();
19436
19437    let both_hunks_expanded = r#"
19438        - a
19439        + ˇA
19440          b
19441        - c
19442        + C
19443        "#
19444    .unindent();
19445
19446    cx.assert_state_with_diff(both_hunks_expanded.clone());
19447
19448    let hunk_ranges = cx.update_editor(|editor, window, cx| {
19449        let snapshot = editor.snapshot(window, cx);
19450        let hunks = editor
19451            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19452            .collect::<Vec<_>>();
19453        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
19454        let buffer_id = hunks[0].buffer_id;
19455        hunks
19456            .into_iter()
19457            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
19458            .collect::<Vec<_>>()
19459    });
19460    assert_eq!(hunk_ranges.len(), 2);
19461
19462    cx.update_editor(|editor, _, cx| {
19463        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19464    });
19465    executor.run_until_parked();
19466
19467    let second_hunk_expanded = r#"
19468          ˇA
19469          b
19470        - c
19471        + C
19472        "#
19473    .unindent();
19474
19475    cx.assert_state_with_diff(second_hunk_expanded);
19476
19477    cx.update_editor(|editor, _, cx| {
19478        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19479    });
19480    executor.run_until_parked();
19481
19482    cx.assert_state_with_diff(both_hunks_expanded.clone());
19483
19484    cx.update_editor(|editor, _, cx| {
19485        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
19486    });
19487    executor.run_until_parked();
19488
19489    let first_hunk_expanded = r#"
19490        - a
19491        + ˇA
19492          b
19493          C
19494        "#
19495    .unindent();
19496
19497    cx.assert_state_with_diff(first_hunk_expanded);
19498
19499    cx.update_editor(|editor, _, cx| {
19500        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
19501    });
19502    executor.run_until_parked();
19503
19504    cx.assert_state_with_diff(both_hunks_expanded);
19505
19506    cx.set_state(
19507        &r#"
19508        ˇA
19509        b
19510        "#
19511        .unindent(),
19512    );
19513    cx.run_until_parked();
19514
19515    // TODO this cursor position seems bad
19516    cx.assert_state_with_diff(
19517        r#"
19518        - ˇa
19519        + A
19520          b
19521        "#
19522        .unindent(),
19523    );
19524
19525    cx.update_editor(|editor, window, cx| {
19526        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19527    });
19528
19529    cx.assert_state_with_diff(
19530        r#"
19531            - ˇa
19532            + A
19533              b
19534            - c
19535            "#
19536        .unindent(),
19537    );
19538
19539    let hunk_ranges = cx.update_editor(|editor, window, cx| {
19540        let snapshot = editor.snapshot(window, cx);
19541        let hunks = editor
19542            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19543            .collect::<Vec<_>>();
19544        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
19545        let buffer_id = hunks[0].buffer_id;
19546        hunks
19547            .into_iter()
19548            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
19549            .collect::<Vec<_>>()
19550    });
19551    assert_eq!(hunk_ranges.len(), 2);
19552
19553    cx.update_editor(|editor, _, cx| {
19554        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
19555    });
19556    executor.run_until_parked();
19557
19558    cx.assert_state_with_diff(
19559        r#"
19560        - ˇa
19561        + A
19562          b
19563        "#
19564        .unindent(),
19565    );
19566}
19567
19568#[gpui::test]
19569async fn test_toggle_deletion_hunk_at_start_of_file(
19570    executor: BackgroundExecutor,
19571    cx: &mut TestAppContext,
19572) {
19573    init_test(cx, |_| {});
19574    let mut cx = EditorTestContext::new(cx).await;
19575
19576    let diff_base = r#"
19577        a
19578        b
19579        c
19580        "#
19581    .unindent();
19582
19583    cx.set_state(
19584        &r#"
19585        ˇb
19586        c
19587        "#
19588        .unindent(),
19589    );
19590    cx.set_head_text(&diff_base);
19591    cx.update_editor(|editor, window, cx| {
19592        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19593    });
19594    executor.run_until_parked();
19595
19596    let hunk_expanded = r#"
19597        - a
19598          ˇb
19599          c
19600        "#
19601    .unindent();
19602
19603    cx.assert_state_with_diff(hunk_expanded.clone());
19604
19605    let hunk_ranges = cx.update_editor(|editor, window, cx| {
19606        let snapshot = editor.snapshot(window, cx);
19607        let hunks = editor
19608            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19609            .collect::<Vec<_>>();
19610        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
19611        let buffer_id = hunks[0].buffer_id;
19612        hunks
19613            .into_iter()
19614            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
19615            .collect::<Vec<_>>()
19616    });
19617    assert_eq!(hunk_ranges.len(), 1);
19618
19619    cx.update_editor(|editor, _, cx| {
19620        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19621    });
19622    executor.run_until_parked();
19623
19624    let hunk_collapsed = r#"
19625          ˇb
19626          c
19627        "#
19628    .unindent();
19629
19630    cx.assert_state_with_diff(hunk_collapsed);
19631
19632    cx.update_editor(|editor, _, cx| {
19633        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19634    });
19635    executor.run_until_parked();
19636
19637    cx.assert_state_with_diff(hunk_expanded);
19638}
19639
19640#[gpui::test]
19641async fn test_display_diff_hunks(cx: &mut TestAppContext) {
19642    init_test(cx, |_| {});
19643
19644    let fs = FakeFs::new(cx.executor());
19645    fs.insert_tree(
19646        path!("/test"),
19647        json!({
19648            ".git": {},
19649            "file-1": "ONE\n",
19650            "file-2": "TWO\n",
19651            "file-3": "THREE\n",
19652        }),
19653    )
19654    .await;
19655
19656    fs.set_head_for_repo(
19657        path!("/test/.git").as_ref(),
19658        &[
19659            ("file-1".into(), "one\n".into()),
19660            ("file-2".into(), "two\n".into()),
19661            ("file-3".into(), "three\n".into()),
19662        ],
19663        "deadbeef",
19664    );
19665
19666    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
19667    let mut buffers = vec![];
19668    for i in 1..=3 {
19669        let buffer = project
19670            .update(cx, |project, cx| {
19671                let path = format!(path!("/test/file-{}"), i);
19672                project.open_local_buffer(path, cx)
19673            })
19674            .await
19675            .unwrap();
19676        buffers.push(buffer);
19677    }
19678
19679    let multibuffer = cx.new(|cx| {
19680        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
19681        multibuffer.set_all_diff_hunks_expanded(cx);
19682        for buffer in &buffers {
19683            let snapshot = buffer.read(cx).snapshot();
19684            multibuffer.set_excerpts_for_path(
19685                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
19686                buffer.clone(),
19687                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
19688                DEFAULT_MULTIBUFFER_CONTEXT,
19689                cx,
19690            );
19691        }
19692        multibuffer
19693    });
19694
19695    let editor = cx.add_window(|window, cx| {
19696        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
19697    });
19698    cx.run_until_parked();
19699
19700    let snapshot = editor
19701        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19702        .unwrap();
19703    let hunks = snapshot
19704        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
19705        .map(|hunk| match hunk {
19706            DisplayDiffHunk::Unfolded {
19707                display_row_range, ..
19708            } => display_row_range,
19709            DisplayDiffHunk::Folded { .. } => unreachable!(),
19710        })
19711        .collect::<Vec<_>>();
19712    assert_eq!(
19713        hunks,
19714        [
19715            DisplayRow(2)..DisplayRow(4),
19716            DisplayRow(7)..DisplayRow(9),
19717            DisplayRow(12)..DisplayRow(14),
19718        ]
19719    );
19720}
19721
19722#[gpui::test]
19723async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
19724    init_test(cx, |_| {});
19725
19726    let mut cx = EditorTestContext::new(cx).await;
19727    cx.set_head_text(indoc! { "
19728        one
19729        two
19730        three
19731        four
19732        five
19733        "
19734    });
19735    cx.set_index_text(indoc! { "
19736        one
19737        two
19738        three
19739        four
19740        five
19741        "
19742    });
19743    cx.set_state(indoc! {"
19744        one
19745        TWO
19746        ˇTHREE
19747        FOUR
19748        five
19749    "});
19750    cx.run_until_parked();
19751    cx.update_editor(|editor, window, cx| {
19752        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19753    });
19754    cx.run_until_parked();
19755    cx.assert_index_text(Some(indoc! {"
19756        one
19757        TWO
19758        THREE
19759        FOUR
19760        five
19761    "}));
19762    cx.set_state(indoc! { "
19763        one
19764        TWO
19765        ˇTHREE-HUNDRED
19766        FOUR
19767        five
19768    "});
19769    cx.run_until_parked();
19770    cx.update_editor(|editor, window, cx| {
19771        let snapshot = editor.snapshot(window, cx);
19772        let hunks = editor
19773            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19774            .collect::<Vec<_>>();
19775        assert_eq!(hunks.len(), 1);
19776        assert_eq!(
19777            hunks[0].status(),
19778            DiffHunkStatus {
19779                kind: DiffHunkStatusKind::Modified,
19780                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
19781            }
19782        );
19783
19784        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19785    });
19786    cx.run_until_parked();
19787    cx.assert_index_text(Some(indoc! {"
19788        one
19789        TWO
19790        THREE-HUNDRED
19791        FOUR
19792        five
19793    "}));
19794}
19795
19796#[gpui::test]
19797fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
19798    init_test(cx, |_| {});
19799
19800    let editor = cx.add_window(|window, cx| {
19801        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
19802        build_editor(buffer, window, cx)
19803    });
19804
19805    let render_args = Arc::new(Mutex::new(None));
19806    let snapshot = editor
19807        .update(cx, |editor, window, cx| {
19808            let snapshot = editor.buffer().read(cx).snapshot(cx);
19809            let range =
19810                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
19811
19812            struct RenderArgs {
19813                row: MultiBufferRow,
19814                folded: bool,
19815                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
19816            }
19817
19818            let crease = Crease::inline(
19819                range,
19820                FoldPlaceholder::test(),
19821                {
19822                    let toggle_callback = render_args.clone();
19823                    move |row, folded, callback, _window, _cx| {
19824                        *toggle_callback.lock() = Some(RenderArgs {
19825                            row,
19826                            folded,
19827                            callback,
19828                        });
19829                        div()
19830                    }
19831                },
19832                |_row, _folded, _window, _cx| div(),
19833            );
19834
19835            editor.insert_creases(Some(crease), cx);
19836            let snapshot = editor.snapshot(window, cx);
19837            let _div =
19838                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
19839            snapshot
19840        })
19841        .unwrap();
19842
19843    let render_args = render_args.lock().take().unwrap();
19844    assert_eq!(render_args.row, MultiBufferRow(1));
19845    assert!(!render_args.folded);
19846    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
19847
19848    cx.update_window(*editor, |_, window, cx| {
19849        (render_args.callback)(true, window, cx)
19850    })
19851    .unwrap();
19852    let snapshot = editor
19853        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19854        .unwrap();
19855    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
19856
19857    cx.update_window(*editor, |_, window, cx| {
19858        (render_args.callback)(false, window, cx)
19859    })
19860    .unwrap();
19861    let snapshot = editor
19862        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19863        .unwrap();
19864    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
19865}
19866
19867#[gpui::test]
19868async fn test_input_text(cx: &mut TestAppContext) {
19869    init_test(cx, |_| {});
19870    let mut cx = EditorTestContext::new(cx).await;
19871
19872    cx.set_state(
19873        &r#"ˇone
19874        two
19875
19876        three
19877        fourˇ
19878        five
19879
19880        siˇx"#
19881            .unindent(),
19882    );
19883
19884    cx.dispatch_action(HandleInput(String::new()));
19885    cx.assert_editor_state(
19886        &r#"ˇone
19887        two
19888
19889        three
19890        fourˇ
19891        five
19892
19893        siˇx"#
19894            .unindent(),
19895    );
19896
19897    cx.dispatch_action(HandleInput("AAAA".to_string()));
19898    cx.assert_editor_state(
19899        &r#"AAAAˇone
19900        two
19901
19902        three
19903        fourAAAAˇ
19904        five
19905
19906        siAAAAˇx"#
19907            .unindent(),
19908    );
19909}
19910
19911#[gpui::test]
19912async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
19913    init_test(cx, |_| {});
19914
19915    let mut cx = EditorTestContext::new(cx).await;
19916    cx.set_state(
19917        r#"let foo = 1;
19918let foo = 2;
19919let foo = 3;
19920let fooˇ = 4;
19921let foo = 5;
19922let foo = 6;
19923let foo = 7;
19924let foo = 8;
19925let foo = 9;
19926let foo = 10;
19927let foo = 11;
19928let foo = 12;
19929let foo = 13;
19930let foo = 14;
19931let foo = 15;"#,
19932    );
19933
19934    cx.update_editor(|e, window, cx| {
19935        assert_eq!(
19936            e.next_scroll_position,
19937            NextScrollCursorCenterTopBottom::Center,
19938            "Default next scroll direction is center",
19939        );
19940
19941        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19942        assert_eq!(
19943            e.next_scroll_position,
19944            NextScrollCursorCenterTopBottom::Top,
19945            "After center, next scroll direction should be top",
19946        );
19947
19948        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19949        assert_eq!(
19950            e.next_scroll_position,
19951            NextScrollCursorCenterTopBottom::Bottom,
19952            "After top, next scroll direction should be bottom",
19953        );
19954
19955        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19956        assert_eq!(
19957            e.next_scroll_position,
19958            NextScrollCursorCenterTopBottom::Center,
19959            "After bottom, scrolling should start over",
19960        );
19961
19962        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19963        assert_eq!(
19964            e.next_scroll_position,
19965            NextScrollCursorCenterTopBottom::Top,
19966            "Scrolling continues if retriggered fast enough"
19967        );
19968    });
19969
19970    cx.executor()
19971        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
19972    cx.executor().run_until_parked();
19973    cx.update_editor(|e, _, _| {
19974        assert_eq!(
19975            e.next_scroll_position,
19976            NextScrollCursorCenterTopBottom::Center,
19977            "If scrolling is not triggered fast enough, it should reset"
19978        );
19979    });
19980}
19981
19982#[gpui::test]
19983async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
19984    init_test(cx, |_| {});
19985    let mut cx = EditorLspTestContext::new_rust(
19986        lsp::ServerCapabilities {
19987            definition_provider: Some(lsp::OneOf::Left(true)),
19988            references_provider: Some(lsp::OneOf::Left(true)),
19989            ..lsp::ServerCapabilities::default()
19990        },
19991        cx,
19992    )
19993    .await;
19994
19995    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
19996        let go_to_definition = cx
19997            .lsp
19998            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
19999                move |params, _| async move {
20000                    if empty_go_to_definition {
20001                        Ok(None)
20002                    } else {
20003                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
20004                            uri: params.text_document_position_params.text_document.uri,
20005                            range: lsp::Range::new(
20006                                lsp::Position::new(4, 3),
20007                                lsp::Position::new(4, 6),
20008                            ),
20009                        })))
20010                    }
20011                },
20012            );
20013        let references = cx
20014            .lsp
20015            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
20016                Ok(Some(vec![lsp::Location {
20017                    uri: params.text_document_position.text_document.uri,
20018                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
20019                }]))
20020            });
20021        (go_to_definition, references)
20022    };
20023
20024    cx.set_state(
20025        &r#"fn one() {
20026            let mut a = ˇtwo();
20027        }
20028
20029        fn two() {}"#
20030            .unindent(),
20031    );
20032    set_up_lsp_handlers(false, &mut cx);
20033    let navigated = cx
20034        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
20035        .await
20036        .expect("Failed to navigate to definition");
20037    assert_eq!(
20038        navigated,
20039        Navigated::Yes,
20040        "Should have navigated to definition from the GetDefinition response"
20041    );
20042    cx.assert_editor_state(
20043        &r#"fn one() {
20044            let mut a = two();
20045        }
20046
20047        fn «twoˇ»() {}"#
20048            .unindent(),
20049    );
20050
20051    let editors = cx.update_workspace(|workspace, _, cx| {
20052        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
20053    });
20054    cx.update_editor(|_, _, test_editor_cx| {
20055        assert_eq!(
20056            editors.len(),
20057            1,
20058            "Initially, only one, test, editor should be open in the workspace"
20059        );
20060        assert_eq!(
20061            test_editor_cx.entity(),
20062            editors.last().expect("Asserted len is 1").clone()
20063        );
20064    });
20065
20066    set_up_lsp_handlers(true, &mut cx);
20067    let navigated = cx
20068        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
20069        .await
20070        .expect("Failed to navigate to lookup references");
20071    assert_eq!(
20072        navigated,
20073        Navigated::Yes,
20074        "Should have navigated to references as a fallback after empty GoToDefinition response"
20075    );
20076    // We should not change the selections in the existing file,
20077    // if opening another milti buffer with the references
20078    cx.assert_editor_state(
20079        &r#"fn one() {
20080            let mut a = two();
20081        }
20082
20083        fn «twoˇ»() {}"#
20084            .unindent(),
20085    );
20086    let editors = cx.update_workspace(|workspace, _, cx| {
20087        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
20088    });
20089    cx.update_editor(|_, _, test_editor_cx| {
20090        assert_eq!(
20091            editors.len(),
20092            2,
20093            "After falling back to references search, we open a new editor with the results"
20094        );
20095        let references_fallback_text = editors
20096            .into_iter()
20097            .find(|new_editor| *new_editor != test_editor_cx.entity())
20098            .expect("Should have one non-test editor now")
20099            .read(test_editor_cx)
20100            .text(test_editor_cx);
20101        assert_eq!(
20102            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
20103            "Should use the range from the references response and not the GoToDefinition one"
20104        );
20105    });
20106}
20107
20108#[gpui::test]
20109async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
20110    init_test(cx, |_| {});
20111    cx.update(|cx| {
20112        let mut editor_settings = EditorSettings::get_global(cx).clone();
20113        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
20114        EditorSettings::override_global(editor_settings, cx);
20115    });
20116    let mut cx = EditorLspTestContext::new_rust(
20117        lsp::ServerCapabilities {
20118            definition_provider: Some(lsp::OneOf::Left(true)),
20119            references_provider: Some(lsp::OneOf::Left(true)),
20120            ..lsp::ServerCapabilities::default()
20121        },
20122        cx,
20123    )
20124    .await;
20125    let original_state = r#"fn one() {
20126        let mut a = ˇtwo();
20127    }
20128
20129    fn two() {}"#
20130        .unindent();
20131    cx.set_state(&original_state);
20132
20133    let mut go_to_definition = cx
20134        .lsp
20135        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
20136            move |_, _| async move { Ok(None) },
20137        );
20138    let _references = cx
20139        .lsp
20140        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
20141            panic!("Should not call for references with no go to definition fallback")
20142        });
20143
20144    let navigated = cx
20145        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
20146        .await
20147        .expect("Failed to navigate to lookup references");
20148    go_to_definition
20149        .next()
20150        .await
20151        .expect("Should have called the go_to_definition handler");
20152
20153    assert_eq!(
20154        navigated,
20155        Navigated::No,
20156        "Should have navigated to references as a fallback after empty GoToDefinition response"
20157    );
20158    cx.assert_editor_state(&original_state);
20159    let editors = cx.update_workspace(|workspace, _, cx| {
20160        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
20161    });
20162    cx.update_editor(|_, _, _| {
20163        assert_eq!(
20164            editors.len(),
20165            1,
20166            "After unsuccessful fallback, no other editor should have been opened"
20167        );
20168    });
20169}
20170
20171#[gpui::test]
20172async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
20173    init_test(cx, |_| {});
20174
20175    let language = Arc::new(Language::new(
20176        LanguageConfig::default(),
20177        Some(tree_sitter_rust::LANGUAGE.into()),
20178    ));
20179
20180    let text = r#"
20181        #[cfg(test)]
20182        mod tests() {
20183            #[test]
20184            fn runnable_1() {
20185                let a = 1;
20186            }
20187
20188            #[test]
20189            fn runnable_2() {
20190                let a = 1;
20191                let b = 2;
20192            }
20193        }
20194    "#
20195    .unindent();
20196
20197    let fs = FakeFs::new(cx.executor());
20198    fs.insert_file("/file.rs", Default::default()).await;
20199
20200    let project = Project::test(fs, ["/a".as_ref()], cx).await;
20201    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20202    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20203    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
20204    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
20205
20206    let editor = cx.new_window_entity(|window, cx| {
20207        Editor::new(
20208            EditorMode::full(),
20209            multi_buffer,
20210            Some(project.clone()),
20211            window,
20212            cx,
20213        )
20214    });
20215
20216    editor.update_in(cx, |editor, window, cx| {
20217        let snapshot = editor.buffer().read(cx).snapshot(cx);
20218        editor.tasks.insert(
20219            (buffer.read(cx).remote_id(), 3),
20220            RunnableTasks {
20221                templates: vec![],
20222                offset: snapshot.anchor_before(43),
20223                column: 0,
20224                extra_variables: HashMap::default(),
20225                context_range: BufferOffset(43)..BufferOffset(85),
20226            },
20227        );
20228        editor.tasks.insert(
20229            (buffer.read(cx).remote_id(), 8),
20230            RunnableTasks {
20231                templates: vec![],
20232                offset: snapshot.anchor_before(86),
20233                column: 0,
20234                extra_variables: HashMap::default(),
20235                context_range: BufferOffset(86)..BufferOffset(191),
20236            },
20237        );
20238
20239        // Test finding task when cursor is inside function body
20240        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20241            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
20242        });
20243        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
20244        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
20245
20246        // Test finding task when cursor is on function name
20247        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20248            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
20249        });
20250        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
20251        assert_eq!(row, 8, "Should find task when cursor is on function name");
20252    });
20253}
20254
20255#[gpui::test]
20256async fn test_folding_buffers(cx: &mut TestAppContext) {
20257    init_test(cx, |_| {});
20258
20259    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
20260    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
20261    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
20262
20263    let fs = FakeFs::new(cx.executor());
20264    fs.insert_tree(
20265        path!("/a"),
20266        json!({
20267            "first.rs": sample_text_1,
20268            "second.rs": sample_text_2,
20269            "third.rs": sample_text_3,
20270        }),
20271    )
20272    .await;
20273    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20274    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20275    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20276    let worktree = project.update(cx, |project, cx| {
20277        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
20278        assert_eq!(worktrees.len(), 1);
20279        worktrees.pop().unwrap()
20280    });
20281    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
20282
20283    let buffer_1 = project
20284        .update(cx, |project, cx| {
20285            project.open_buffer((worktree_id, "first.rs"), cx)
20286        })
20287        .await
20288        .unwrap();
20289    let buffer_2 = project
20290        .update(cx, |project, cx| {
20291            project.open_buffer((worktree_id, "second.rs"), cx)
20292        })
20293        .await
20294        .unwrap();
20295    let buffer_3 = project
20296        .update(cx, |project, cx| {
20297            project.open_buffer((worktree_id, "third.rs"), cx)
20298        })
20299        .await
20300        .unwrap();
20301
20302    let multi_buffer = cx.new(|cx| {
20303        let mut multi_buffer = MultiBuffer::new(ReadWrite);
20304        multi_buffer.push_excerpts(
20305            buffer_1.clone(),
20306            [
20307                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20308                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20309                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20310            ],
20311            cx,
20312        );
20313        multi_buffer.push_excerpts(
20314            buffer_2.clone(),
20315            [
20316                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20317                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20318                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20319            ],
20320            cx,
20321        );
20322        multi_buffer.push_excerpts(
20323            buffer_3.clone(),
20324            [
20325                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20326                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20327                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20328            ],
20329            cx,
20330        );
20331        multi_buffer
20332    });
20333    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20334        Editor::new(
20335            EditorMode::full(),
20336            multi_buffer.clone(),
20337            Some(project.clone()),
20338            window,
20339            cx,
20340        )
20341    });
20342
20343    assert_eq!(
20344        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20345        "\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",
20346    );
20347
20348    multi_buffer_editor.update(cx, |editor, cx| {
20349        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
20350    });
20351    assert_eq!(
20352        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20353        "\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",
20354        "After folding the first buffer, its text should not be displayed"
20355    );
20356
20357    multi_buffer_editor.update(cx, |editor, cx| {
20358        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
20359    });
20360    assert_eq!(
20361        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20362        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
20363        "After folding the second buffer, its text should not be displayed"
20364    );
20365
20366    multi_buffer_editor.update(cx, |editor, cx| {
20367        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
20368    });
20369    assert_eq!(
20370        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20371        "\n\n\n\n\n",
20372        "After folding the third buffer, its text should not be displayed"
20373    );
20374
20375    // Emulate selection inside the fold logic, that should work
20376    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20377        editor
20378            .snapshot(window, cx)
20379            .next_line_boundary(Point::new(0, 4));
20380    });
20381
20382    multi_buffer_editor.update(cx, |editor, cx| {
20383        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
20384    });
20385    assert_eq!(
20386        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20387        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
20388        "After unfolding the second buffer, its text should be displayed"
20389    );
20390
20391    // Typing inside of buffer 1 causes that buffer to be unfolded.
20392    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20393        assert_eq!(
20394            multi_buffer
20395                .read(cx)
20396                .snapshot(cx)
20397                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
20398                .collect::<String>(),
20399            "bbbb"
20400        );
20401        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
20402            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
20403        });
20404        editor.handle_input("B", window, cx);
20405    });
20406
20407    assert_eq!(
20408        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20409        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
20410        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
20411    );
20412
20413    multi_buffer_editor.update(cx, |editor, cx| {
20414        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
20415    });
20416    assert_eq!(
20417        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20418        "\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",
20419        "After unfolding the all buffers, all original text should be displayed"
20420    );
20421}
20422
20423#[gpui::test]
20424async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
20425    init_test(cx, |_| {});
20426
20427    let sample_text_1 = "1111\n2222\n3333".to_string();
20428    let sample_text_2 = "4444\n5555\n6666".to_string();
20429    let sample_text_3 = "7777\n8888\n9999".to_string();
20430
20431    let fs = FakeFs::new(cx.executor());
20432    fs.insert_tree(
20433        path!("/a"),
20434        json!({
20435            "first.rs": sample_text_1,
20436            "second.rs": sample_text_2,
20437            "third.rs": sample_text_3,
20438        }),
20439    )
20440    .await;
20441    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20442    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20443    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20444    let worktree = project.update(cx, |project, cx| {
20445        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
20446        assert_eq!(worktrees.len(), 1);
20447        worktrees.pop().unwrap()
20448    });
20449    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
20450
20451    let buffer_1 = project
20452        .update(cx, |project, cx| {
20453            project.open_buffer((worktree_id, "first.rs"), cx)
20454        })
20455        .await
20456        .unwrap();
20457    let buffer_2 = project
20458        .update(cx, |project, cx| {
20459            project.open_buffer((worktree_id, "second.rs"), cx)
20460        })
20461        .await
20462        .unwrap();
20463    let buffer_3 = project
20464        .update(cx, |project, cx| {
20465            project.open_buffer((worktree_id, "third.rs"), cx)
20466        })
20467        .await
20468        .unwrap();
20469
20470    let multi_buffer = cx.new(|cx| {
20471        let mut multi_buffer = MultiBuffer::new(ReadWrite);
20472        multi_buffer.push_excerpts(
20473            buffer_1.clone(),
20474            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
20475            cx,
20476        );
20477        multi_buffer.push_excerpts(
20478            buffer_2.clone(),
20479            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
20480            cx,
20481        );
20482        multi_buffer.push_excerpts(
20483            buffer_3.clone(),
20484            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
20485            cx,
20486        );
20487        multi_buffer
20488    });
20489
20490    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20491        Editor::new(
20492            EditorMode::full(),
20493            multi_buffer,
20494            Some(project.clone()),
20495            window,
20496            cx,
20497        )
20498    });
20499
20500    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
20501    assert_eq!(
20502        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20503        full_text,
20504    );
20505
20506    multi_buffer_editor.update(cx, |editor, cx| {
20507        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
20508    });
20509    assert_eq!(
20510        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20511        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
20512        "After folding the first buffer, its text should not be displayed"
20513    );
20514
20515    multi_buffer_editor.update(cx, |editor, cx| {
20516        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
20517    });
20518
20519    assert_eq!(
20520        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20521        "\n\n\n\n\n\n7777\n8888\n9999",
20522        "After folding the second buffer, its text should not be displayed"
20523    );
20524
20525    multi_buffer_editor.update(cx, |editor, cx| {
20526        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
20527    });
20528    assert_eq!(
20529        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20530        "\n\n\n\n\n",
20531        "After folding the third buffer, its text should not be displayed"
20532    );
20533
20534    multi_buffer_editor.update(cx, |editor, cx| {
20535        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
20536    });
20537    assert_eq!(
20538        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20539        "\n\n\n\n4444\n5555\n6666\n\n",
20540        "After unfolding the second buffer, its text should be displayed"
20541    );
20542
20543    multi_buffer_editor.update(cx, |editor, cx| {
20544        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
20545    });
20546    assert_eq!(
20547        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20548        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
20549        "After unfolding the first buffer, its text should be displayed"
20550    );
20551
20552    multi_buffer_editor.update(cx, |editor, cx| {
20553        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
20554    });
20555    assert_eq!(
20556        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20557        full_text,
20558        "After unfolding all buffers, all original text should be displayed"
20559    );
20560}
20561
20562#[gpui::test]
20563async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
20564    init_test(cx, |_| {});
20565
20566    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
20567
20568    let fs = FakeFs::new(cx.executor());
20569    fs.insert_tree(
20570        path!("/a"),
20571        json!({
20572            "main.rs": sample_text,
20573        }),
20574    )
20575    .await;
20576    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20577    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20578    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20579    let worktree = project.update(cx, |project, cx| {
20580        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
20581        assert_eq!(worktrees.len(), 1);
20582        worktrees.pop().unwrap()
20583    });
20584    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
20585
20586    let buffer_1 = project
20587        .update(cx, |project, cx| {
20588            project.open_buffer((worktree_id, "main.rs"), cx)
20589        })
20590        .await
20591        .unwrap();
20592
20593    let multi_buffer = cx.new(|cx| {
20594        let mut multi_buffer = MultiBuffer::new(ReadWrite);
20595        multi_buffer.push_excerpts(
20596            buffer_1.clone(),
20597            [ExcerptRange::new(
20598                Point::new(0, 0)
20599                    ..Point::new(
20600                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
20601                        0,
20602                    ),
20603            )],
20604            cx,
20605        );
20606        multi_buffer
20607    });
20608    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20609        Editor::new(
20610            EditorMode::full(),
20611            multi_buffer,
20612            Some(project.clone()),
20613            window,
20614            cx,
20615        )
20616    });
20617
20618    let selection_range = Point::new(1, 0)..Point::new(2, 0);
20619    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20620        enum TestHighlight {}
20621        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
20622        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
20623        editor.highlight_text::<TestHighlight>(
20624            vec![highlight_range.clone()],
20625            HighlightStyle::color(Hsla::green()),
20626            cx,
20627        );
20628        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20629            s.select_ranges(Some(highlight_range))
20630        });
20631    });
20632
20633    let full_text = format!("\n\n{sample_text}");
20634    assert_eq!(
20635        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20636        full_text,
20637    );
20638}
20639
20640#[gpui::test]
20641async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
20642    init_test(cx, |_| {});
20643    cx.update(|cx| {
20644        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
20645            "keymaps/default-linux.json",
20646            cx,
20647        )
20648        .unwrap();
20649        cx.bind_keys(default_key_bindings);
20650    });
20651
20652    let (editor, cx) = cx.add_window_view(|window, cx| {
20653        let multi_buffer = MultiBuffer::build_multi(
20654            [
20655                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
20656                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
20657                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
20658                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
20659            ],
20660            cx,
20661        );
20662        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
20663
20664        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
20665        // fold all but the second buffer, so that we test navigating between two
20666        // adjacent folded buffers, as well as folded buffers at the start and
20667        // end the multibuffer
20668        editor.fold_buffer(buffer_ids[0], cx);
20669        editor.fold_buffer(buffer_ids[2], cx);
20670        editor.fold_buffer(buffer_ids[3], cx);
20671
20672        editor
20673    });
20674    cx.simulate_resize(size(px(1000.), px(1000.)));
20675
20676    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
20677    cx.assert_excerpts_with_selections(indoc! {"
20678        [EXCERPT]
20679        ˇ[FOLDED]
20680        [EXCERPT]
20681        a1
20682        b1
20683        [EXCERPT]
20684        [FOLDED]
20685        [EXCERPT]
20686        [FOLDED]
20687        "
20688    });
20689    cx.simulate_keystroke("down");
20690    cx.assert_excerpts_with_selections(indoc! {"
20691        [EXCERPT]
20692        [FOLDED]
20693        [EXCERPT]
20694        ˇa1
20695        b1
20696        [EXCERPT]
20697        [FOLDED]
20698        [EXCERPT]
20699        [FOLDED]
20700        "
20701    });
20702    cx.simulate_keystroke("down");
20703    cx.assert_excerpts_with_selections(indoc! {"
20704        [EXCERPT]
20705        [FOLDED]
20706        [EXCERPT]
20707        a1
20708        ˇb1
20709        [EXCERPT]
20710        [FOLDED]
20711        [EXCERPT]
20712        [FOLDED]
20713        "
20714    });
20715    cx.simulate_keystroke("down");
20716    cx.assert_excerpts_with_selections(indoc! {"
20717        [EXCERPT]
20718        [FOLDED]
20719        [EXCERPT]
20720        a1
20721        b1
20722        ˇ[EXCERPT]
20723        [FOLDED]
20724        [EXCERPT]
20725        [FOLDED]
20726        "
20727    });
20728    cx.simulate_keystroke("down");
20729    cx.assert_excerpts_with_selections(indoc! {"
20730        [EXCERPT]
20731        [FOLDED]
20732        [EXCERPT]
20733        a1
20734        b1
20735        [EXCERPT]
20736        ˇ[FOLDED]
20737        [EXCERPT]
20738        [FOLDED]
20739        "
20740    });
20741    for _ in 0..5 {
20742        cx.simulate_keystroke("down");
20743        cx.assert_excerpts_with_selections(indoc! {"
20744            [EXCERPT]
20745            [FOLDED]
20746            [EXCERPT]
20747            a1
20748            b1
20749            [EXCERPT]
20750            [FOLDED]
20751            [EXCERPT]
20752            ˇ[FOLDED]
20753            "
20754        });
20755    }
20756
20757    cx.simulate_keystroke("up");
20758    cx.assert_excerpts_with_selections(indoc! {"
20759        [EXCERPT]
20760        [FOLDED]
20761        [EXCERPT]
20762        a1
20763        b1
20764        [EXCERPT]
20765        ˇ[FOLDED]
20766        [EXCERPT]
20767        [FOLDED]
20768        "
20769    });
20770    cx.simulate_keystroke("up");
20771    cx.assert_excerpts_with_selections(indoc! {"
20772        [EXCERPT]
20773        [FOLDED]
20774        [EXCERPT]
20775        a1
20776        b1
20777        ˇ[EXCERPT]
20778        [FOLDED]
20779        [EXCERPT]
20780        [FOLDED]
20781        "
20782    });
20783    cx.simulate_keystroke("up");
20784    cx.assert_excerpts_with_selections(indoc! {"
20785        [EXCERPT]
20786        [FOLDED]
20787        [EXCERPT]
20788        a1
20789        ˇb1
20790        [EXCERPT]
20791        [FOLDED]
20792        [EXCERPT]
20793        [FOLDED]
20794        "
20795    });
20796    cx.simulate_keystroke("up");
20797    cx.assert_excerpts_with_selections(indoc! {"
20798        [EXCERPT]
20799        [FOLDED]
20800        [EXCERPT]
20801        ˇa1
20802        b1
20803        [EXCERPT]
20804        [FOLDED]
20805        [EXCERPT]
20806        [FOLDED]
20807        "
20808    });
20809    for _ in 0..5 {
20810        cx.simulate_keystroke("up");
20811        cx.assert_excerpts_with_selections(indoc! {"
20812            [EXCERPT]
20813            ˇ[FOLDED]
20814            [EXCERPT]
20815            a1
20816            b1
20817            [EXCERPT]
20818            [FOLDED]
20819            [EXCERPT]
20820            [FOLDED]
20821            "
20822        });
20823    }
20824}
20825
20826#[gpui::test]
20827async fn test_edit_prediction_text(cx: &mut TestAppContext) {
20828    init_test(cx, |_| {});
20829
20830    // Simple insertion
20831    assert_highlighted_edits(
20832        "Hello, world!",
20833        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
20834        true,
20835        cx,
20836        |highlighted_edits, cx| {
20837            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
20838            assert_eq!(highlighted_edits.highlights.len(), 1);
20839            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
20840            assert_eq!(
20841                highlighted_edits.highlights[0].1.background_color,
20842                Some(cx.theme().status().created_background)
20843            );
20844        },
20845    )
20846    .await;
20847
20848    // Replacement
20849    assert_highlighted_edits(
20850        "This is a test.",
20851        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
20852        false,
20853        cx,
20854        |highlighted_edits, cx| {
20855            assert_eq!(highlighted_edits.text, "That is a test.");
20856            assert_eq!(highlighted_edits.highlights.len(), 1);
20857            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
20858            assert_eq!(
20859                highlighted_edits.highlights[0].1.background_color,
20860                Some(cx.theme().status().created_background)
20861            );
20862        },
20863    )
20864    .await;
20865
20866    // Multiple edits
20867    assert_highlighted_edits(
20868        "Hello, world!",
20869        vec![
20870            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
20871            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
20872        ],
20873        false,
20874        cx,
20875        |highlighted_edits, cx| {
20876            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
20877            assert_eq!(highlighted_edits.highlights.len(), 2);
20878            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
20879            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
20880            assert_eq!(
20881                highlighted_edits.highlights[0].1.background_color,
20882                Some(cx.theme().status().created_background)
20883            );
20884            assert_eq!(
20885                highlighted_edits.highlights[1].1.background_color,
20886                Some(cx.theme().status().created_background)
20887            );
20888        },
20889    )
20890    .await;
20891
20892    // Multiple lines with edits
20893    assert_highlighted_edits(
20894        "First line\nSecond line\nThird line\nFourth line",
20895        vec![
20896            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
20897            (
20898                Point::new(2, 0)..Point::new(2, 10),
20899                "New third line".to_string(),
20900            ),
20901            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
20902        ],
20903        false,
20904        cx,
20905        |highlighted_edits, cx| {
20906            assert_eq!(
20907                highlighted_edits.text,
20908                "Second modified\nNew third line\nFourth updated line"
20909            );
20910            assert_eq!(highlighted_edits.highlights.len(), 3);
20911            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
20912            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
20913            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
20914            for highlight in &highlighted_edits.highlights {
20915                assert_eq!(
20916                    highlight.1.background_color,
20917                    Some(cx.theme().status().created_background)
20918                );
20919            }
20920        },
20921    )
20922    .await;
20923}
20924
20925#[gpui::test]
20926async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
20927    init_test(cx, |_| {});
20928
20929    // Deletion
20930    assert_highlighted_edits(
20931        "Hello, world!",
20932        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
20933        true,
20934        cx,
20935        |highlighted_edits, cx| {
20936            assert_eq!(highlighted_edits.text, "Hello, world!");
20937            assert_eq!(highlighted_edits.highlights.len(), 1);
20938            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
20939            assert_eq!(
20940                highlighted_edits.highlights[0].1.background_color,
20941                Some(cx.theme().status().deleted_background)
20942            );
20943        },
20944    )
20945    .await;
20946
20947    // Insertion
20948    assert_highlighted_edits(
20949        "Hello, world!",
20950        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
20951        true,
20952        cx,
20953        |highlighted_edits, cx| {
20954            assert_eq!(highlighted_edits.highlights.len(), 1);
20955            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
20956            assert_eq!(
20957                highlighted_edits.highlights[0].1.background_color,
20958                Some(cx.theme().status().created_background)
20959            );
20960        },
20961    )
20962    .await;
20963}
20964
20965async fn assert_highlighted_edits(
20966    text: &str,
20967    edits: Vec<(Range<Point>, String)>,
20968    include_deletions: bool,
20969    cx: &mut TestAppContext,
20970    assertion_fn: impl Fn(HighlightedText, &App),
20971) {
20972    let window = cx.add_window(|window, cx| {
20973        let buffer = MultiBuffer::build_simple(text, cx);
20974        Editor::new(EditorMode::full(), buffer, None, window, cx)
20975    });
20976    let cx = &mut VisualTestContext::from_window(*window, cx);
20977
20978    let (buffer, snapshot) = window
20979        .update(cx, |editor, _window, cx| {
20980            (
20981                editor.buffer().clone(),
20982                editor.buffer().read(cx).snapshot(cx),
20983            )
20984        })
20985        .unwrap();
20986
20987    let edits = edits
20988        .into_iter()
20989        .map(|(range, edit)| {
20990            (
20991                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
20992                edit,
20993            )
20994        })
20995        .collect::<Vec<_>>();
20996
20997    let text_anchor_edits = edits
20998        .clone()
20999        .into_iter()
21000        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
21001        .collect::<Vec<_>>();
21002
21003    let edit_preview = window
21004        .update(cx, |_, _window, cx| {
21005            buffer
21006                .read(cx)
21007                .as_singleton()
21008                .unwrap()
21009                .read(cx)
21010                .preview_edits(text_anchor_edits.into(), cx)
21011        })
21012        .unwrap()
21013        .await;
21014
21015    cx.update(|_window, cx| {
21016        let highlighted_edits = edit_prediction_edit_text(
21017            snapshot.as_singleton().unwrap().2,
21018            &edits,
21019            &edit_preview,
21020            include_deletions,
21021            cx,
21022        );
21023        assertion_fn(highlighted_edits, cx)
21024    });
21025}
21026
21027#[track_caller]
21028fn assert_breakpoint(
21029    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
21030    path: &Arc<Path>,
21031    expected: Vec<(u32, Breakpoint)>,
21032) {
21033    if expected.is_empty() {
21034        assert!(!breakpoints.contains_key(path), "{}", path.display());
21035    } else {
21036        let mut breakpoint = breakpoints
21037            .get(path)
21038            .unwrap()
21039            .iter()
21040            .map(|breakpoint| {
21041                (
21042                    breakpoint.row,
21043                    Breakpoint {
21044                        message: breakpoint.message.clone(),
21045                        state: breakpoint.state,
21046                        condition: breakpoint.condition.clone(),
21047                        hit_condition: breakpoint.hit_condition.clone(),
21048                    },
21049                )
21050            })
21051            .collect::<Vec<_>>();
21052
21053        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
21054
21055        assert_eq!(expected, breakpoint);
21056    }
21057}
21058
21059fn add_log_breakpoint_at_cursor(
21060    editor: &mut Editor,
21061    log_message: &str,
21062    window: &mut Window,
21063    cx: &mut Context<Editor>,
21064) {
21065    let (anchor, bp) = editor
21066        .breakpoints_at_cursors(window, cx)
21067        .first()
21068        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
21069        .unwrap_or_else(|| {
21070            let cursor_position: Point = editor.selections.newest(cx).head();
21071
21072            let breakpoint_position = editor
21073                .snapshot(window, cx)
21074                .display_snapshot
21075                .buffer_snapshot
21076                .anchor_before(Point::new(cursor_position.row, 0));
21077
21078            (breakpoint_position, Breakpoint::new_log(log_message))
21079        });
21080
21081    editor.edit_breakpoint_at_anchor(
21082        anchor,
21083        bp,
21084        BreakpointEditAction::EditLogMessage(log_message.into()),
21085        cx,
21086    );
21087}
21088
21089#[gpui::test]
21090async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
21091    init_test(cx, |_| {});
21092
21093    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
21094    let fs = FakeFs::new(cx.executor());
21095    fs.insert_tree(
21096        path!("/a"),
21097        json!({
21098            "main.rs": sample_text,
21099        }),
21100    )
21101    .await;
21102    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21103    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21104    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21105
21106    let fs = FakeFs::new(cx.executor());
21107    fs.insert_tree(
21108        path!("/a"),
21109        json!({
21110            "main.rs": sample_text,
21111        }),
21112    )
21113    .await;
21114    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21115    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21116    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21117    let worktree_id = workspace
21118        .update(cx, |workspace, _window, cx| {
21119            workspace.project().update(cx, |project, cx| {
21120                project.worktrees(cx).next().unwrap().read(cx).id()
21121            })
21122        })
21123        .unwrap();
21124
21125    let buffer = project
21126        .update(cx, |project, cx| {
21127            project.open_buffer((worktree_id, "main.rs"), cx)
21128        })
21129        .await
21130        .unwrap();
21131
21132    let (editor, cx) = cx.add_window_view(|window, cx| {
21133        Editor::new(
21134            EditorMode::full(),
21135            MultiBuffer::build_from_buffer(buffer, cx),
21136            Some(project.clone()),
21137            window,
21138            cx,
21139        )
21140    });
21141
21142    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
21143    let abs_path = project.read_with(cx, |project, cx| {
21144        project
21145            .absolute_path(&project_path, cx)
21146            .map(Arc::from)
21147            .unwrap()
21148    });
21149
21150    // assert we can add breakpoint on the first line
21151    editor.update_in(cx, |editor, window, cx| {
21152        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21153        editor.move_to_end(&MoveToEnd, window, cx);
21154        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21155    });
21156
21157    let breakpoints = editor.update(cx, |editor, cx| {
21158        editor
21159            .breakpoint_store()
21160            .as_ref()
21161            .unwrap()
21162            .read(cx)
21163            .all_source_breakpoints(cx)
21164    });
21165
21166    assert_eq!(1, breakpoints.len());
21167    assert_breakpoint(
21168        &breakpoints,
21169        &abs_path,
21170        vec![
21171            (0, Breakpoint::new_standard()),
21172            (3, Breakpoint::new_standard()),
21173        ],
21174    );
21175
21176    editor.update_in(cx, |editor, window, cx| {
21177        editor.move_to_beginning(&MoveToBeginning, window, cx);
21178        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21179    });
21180
21181    let breakpoints = editor.update(cx, |editor, cx| {
21182        editor
21183            .breakpoint_store()
21184            .as_ref()
21185            .unwrap()
21186            .read(cx)
21187            .all_source_breakpoints(cx)
21188    });
21189
21190    assert_eq!(1, breakpoints.len());
21191    assert_breakpoint(
21192        &breakpoints,
21193        &abs_path,
21194        vec![(3, Breakpoint::new_standard())],
21195    );
21196
21197    editor.update_in(cx, |editor, window, cx| {
21198        editor.move_to_end(&MoveToEnd, window, cx);
21199        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21200    });
21201
21202    let breakpoints = editor.update(cx, |editor, cx| {
21203        editor
21204            .breakpoint_store()
21205            .as_ref()
21206            .unwrap()
21207            .read(cx)
21208            .all_source_breakpoints(cx)
21209    });
21210
21211    assert_eq!(0, breakpoints.len());
21212    assert_breakpoint(&breakpoints, &abs_path, vec![]);
21213}
21214
21215#[gpui::test]
21216async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
21217    init_test(cx, |_| {});
21218
21219    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
21220
21221    let fs = FakeFs::new(cx.executor());
21222    fs.insert_tree(
21223        path!("/a"),
21224        json!({
21225            "main.rs": sample_text,
21226        }),
21227    )
21228    .await;
21229    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21230    let (workspace, cx) =
21231        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21232
21233    let worktree_id = workspace.update(cx, |workspace, cx| {
21234        workspace.project().update(cx, |project, cx| {
21235            project.worktrees(cx).next().unwrap().read(cx).id()
21236        })
21237    });
21238
21239    let buffer = project
21240        .update(cx, |project, cx| {
21241            project.open_buffer((worktree_id, "main.rs"), cx)
21242        })
21243        .await
21244        .unwrap();
21245
21246    let (editor, cx) = cx.add_window_view(|window, cx| {
21247        Editor::new(
21248            EditorMode::full(),
21249            MultiBuffer::build_from_buffer(buffer, cx),
21250            Some(project.clone()),
21251            window,
21252            cx,
21253        )
21254    });
21255
21256    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
21257    let abs_path = project.read_with(cx, |project, cx| {
21258        project
21259            .absolute_path(&project_path, cx)
21260            .map(Arc::from)
21261            .unwrap()
21262    });
21263
21264    editor.update_in(cx, |editor, window, cx| {
21265        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
21266    });
21267
21268    let breakpoints = editor.update(cx, |editor, cx| {
21269        editor
21270            .breakpoint_store()
21271            .as_ref()
21272            .unwrap()
21273            .read(cx)
21274            .all_source_breakpoints(cx)
21275    });
21276
21277    assert_breakpoint(
21278        &breakpoints,
21279        &abs_path,
21280        vec![(0, Breakpoint::new_log("hello world"))],
21281    );
21282
21283    // Removing a log message from a log breakpoint should remove it
21284    editor.update_in(cx, |editor, window, cx| {
21285        add_log_breakpoint_at_cursor(editor, "", window, cx);
21286    });
21287
21288    let breakpoints = editor.update(cx, |editor, cx| {
21289        editor
21290            .breakpoint_store()
21291            .as_ref()
21292            .unwrap()
21293            .read(cx)
21294            .all_source_breakpoints(cx)
21295    });
21296
21297    assert_breakpoint(&breakpoints, &abs_path, vec![]);
21298
21299    editor.update_in(cx, |editor, window, cx| {
21300        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21301        editor.move_to_end(&MoveToEnd, window, cx);
21302        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21303        // Not adding a log message to a standard breakpoint shouldn't remove it
21304        add_log_breakpoint_at_cursor(editor, "", window, cx);
21305    });
21306
21307    let breakpoints = editor.update(cx, |editor, cx| {
21308        editor
21309            .breakpoint_store()
21310            .as_ref()
21311            .unwrap()
21312            .read(cx)
21313            .all_source_breakpoints(cx)
21314    });
21315
21316    assert_breakpoint(
21317        &breakpoints,
21318        &abs_path,
21319        vec![
21320            (0, Breakpoint::new_standard()),
21321            (3, Breakpoint::new_standard()),
21322        ],
21323    );
21324
21325    editor.update_in(cx, |editor, window, cx| {
21326        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
21327    });
21328
21329    let breakpoints = editor.update(cx, |editor, cx| {
21330        editor
21331            .breakpoint_store()
21332            .as_ref()
21333            .unwrap()
21334            .read(cx)
21335            .all_source_breakpoints(cx)
21336    });
21337
21338    assert_breakpoint(
21339        &breakpoints,
21340        &abs_path,
21341        vec![
21342            (0, Breakpoint::new_standard()),
21343            (3, Breakpoint::new_log("hello world")),
21344        ],
21345    );
21346
21347    editor.update_in(cx, |editor, window, cx| {
21348        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
21349    });
21350
21351    let breakpoints = editor.update(cx, |editor, cx| {
21352        editor
21353            .breakpoint_store()
21354            .as_ref()
21355            .unwrap()
21356            .read(cx)
21357            .all_source_breakpoints(cx)
21358    });
21359
21360    assert_breakpoint(
21361        &breakpoints,
21362        &abs_path,
21363        vec![
21364            (0, Breakpoint::new_standard()),
21365            (3, Breakpoint::new_log("hello Earth!!")),
21366        ],
21367    );
21368}
21369
21370/// This also tests that Editor::breakpoint_at_cursor_head is working properly
21371/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
21372/// or when breakpoints were placed out of order. This tests for a regression too
21373#[gpui::test]
21374async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
21375    init_test(cx, |_| {});
21376
21377    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
21378    let fs = FakeFs::new(cx.executor());
21379    fs.insert_tree(
21380        path!("/a"),
21381        json!({
21382            "main.rs": sample_text,
21383        }),
21384    )
21385    .await;
21386    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21387    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21388    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21389
21390    let fs = FakeFs::new(cx.executor());
21391    fs.insert_tree(
21392        path!("/a"),
21393        json!({
21394            "main.rs": sample_text,
21395        }),
21396    )
21397    .await;
21398    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21399    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21400    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21401    let worktree_id = workspace
21402        .update(cx, |workspace, _window, cx| {
21403            workspace.project().update(cx, |project, cx| {
21404                project.worktrees(cx).next().unwrap().read(cx).id()
21405            })
21406        })
21407        .unwrap();
21408
21409    let buffer = project
21410        .update(cx, |project, cx| {
21411            project.open_buffer((worktree_id, "main.rs"), cx)
21412        })
21413        .await
21414        .unwrap();
21415
21416    let (editor, cx) = cx.add_window_view(|window, cx| {
21417        Editor::new(
21418            EditorMode::full(),
21419            MultiBuffer::build_from_buffer(buffer, cx),
21420            Some(project.clone()),
21421            window,
21422            cx,
21423        )
21424    });
21425
21426    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
21427    let abs_path = project.read_with(cx, |project, cx| {
21428        project
21429            .absolute_path(&project_path, cx)
21430            .map(Arc::from)
21431            .unwrap()
21432    });
21433
21434    // assert we can add breakpoint on the first line
21435    editor.update_in(cx, |editor, window, cx| {
21436        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21437        editor.move_to_end(&MoveToEnd, window, cx);
21438        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21439        editor.move_up(&MoveUp, window, cx);
21440        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21441    });
21442
21443    let breakpoints = editor.update(cx, |editor, cx| {
21444        editor
21445            .breakpoint_store()
21446            .as_ref()
21447            .unwrap()
21448            .read(cx)
21449            .all_source_breakpoints(cx)
21450    });
21451
21452    assert_eq!(1, breakpoints.len());
21453    assert_breakpoint(
21454        &breakpoints,
21455        &abs_path,
21456        vec![
21457            (0, Breakpoint::new_standard()),
21458            (2, Breakpoint::new_standard()),
21459            (3, Breakpoint::new_standard()),
21460        ],
21461    );
21462
21463    editor.update_in(cx, |editor, window, cx| {
21464        editor.move_to_beginning(&MoveToBeginning, window, cx);
21465        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21466        editor.move_to_end(&MoveToEnd, window, cx);
21467        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21468        // Disabling a breakpoint that doesn't exist should do nothing
21469        editor.move_up(&MoveUp, window, cx);
21470        editor.move_up(&MoveUp, window, cx);
21471        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21472    });
21473
21474    let breakpoints = editor.update(cx, |editor, cx| {
21475        editor
21476            .breakpoint_store()
21477            .as_ref()
21478            .unwrap()
21479            .read(cx)
21480            .all_source_breakpoints(cx)
21481    });
21482
21483    let disable_breakpoint = {
21484        let mut bp = Breakpoint::new_standard();
21485        bp.state = BreakpointState::Disabled;
21486        bp
21487    };
21488
21489    assert_eq!(1, breakpoints.len());
21490    assert_breakpoint(
21491        &breakpoints,
21492        &abs_path,
21493        vec![
21494            (0, disable_breakpoint.clone()),
21495            (2, Breakpoint::new_standard()),
21496            (3, disable_breakpoint.clone()),
21497        ],
21498    );
21499
21500    editor.update_in(cx, |editor, window, cx| {
21501        editor.move_to_beginning(&MoveToBeginning, window, cx);
21502        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
21503        editor.move_to_end(&MoveToEnd, window, cx);
21504        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
21505        editor.move_up(&MoveUp, window, cx);
21506        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21507    });
21508
21509    let breakpoints = editor.update(cx, |editor, cx| {
21510        editor
21511            .breakpoint_store()
21512            .as_ref()
21513            .unwrap()
21514            .read(cx)
21515            .all_source_breakpoints(cx)
21516    });
21517
21518    assert_eq!(1, breakpoints.len());
21519    assert_breakpoint(
21520        &breakpoints,
21521        &abs_path,
21522        vec![
21523            (0, Breakpoint::new_standard()),
21524            (2, disable_breakpoint),
21525            (3, Breakpoint::new_standard()),
21526        ],
21527    );
21528}
21529
21530#[gpui::test]
21531async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
21532    init_test(cx, |_| {});
21533    let capabilities = lsp::ServerCapabilities {
21534        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
21535            prepare_provider: Some(true),
21536            work_done_progress_options: Default::default(),
21537        })),
21538        ..Default::default()
21539    };
21540    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
21541
21542    cx.set_state(indoc! {"
21543        struct Fˇoo {}
21544    "});
21545
21546    cx.update_editor(|editor, _, cx| {
21547        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
21548        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
21549        editor.highlight_background::<DocumentHighlightRead>(
21550            &[highlight_range],
21551            |theme| theme.colors().editor_document_highlight_read_background,
21552            cx,
21553        );
21554    });
21555
21556    let mut prepare_rename_handler = cx
21557        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
21558            move |_, _, _| async move {
21559                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
21560                    start: lsp::Position {
21561                        line: 0,
21562                        character: 7,
21563                    },
21564                    end: lsp::Position {
21565                        line: 0,
21566                        character: 10,
21567                    },
21568                })))
21569            },
21570        );
21571    let prepare_rename_task = cx
21572        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
21573        .expect("Prepare rename was not started");
21574    prepare_rename_handler.next().await.unwrap();
21575    prepare_rename_task.await.expect("Prepare rename failed");
21576
21577    let mut rename_handler =
21578        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
21579            let edit = lsp::TextEdit {
21580                range: lsp::Range {
21581                    start: lsp::Position {
21582                        line: 0,
21583                        character: 7,
21584                    },
21585                    end: lsp::Position {
21586                        line: 0,
21587                        character: 10,
21588                    },
21589                },
21590                new_text: "FooRenamed".to_string(),
21591            };
21592            Ok(Some(lsp::WorkspaceEdit::new(
21593                // Specify the same edit twice
21594                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
21595            )))
21596        });
21597    let rename_task = cx
21598        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
21599        .expect("Confirm rename was not started");
21600    rename_handler.next().await.unwrap();
21601    rename_task.await.expect("Confirm rename failed");
21602    cx.run_until_parked();
21603
21604    // Despite two edits, only one is actually applied as those are identical
21605    cx.assert_editor_state(indoc! {"
21606        struct FooRenamedˇ {}
21607    "});
21608}
21609
21610#[gpui::test]
21611async fn test_rename_without_prepare(cx: &mut TestAppContext) {
21612    init_test(cx, |_| {});
21613    // These capabilities indicate that the server does not support prepare rename.
21614    let capabilities = lsp::ServerCapabilities {
21615        rename_provider: Some(lsp::OneOf::Left(true)),
21616        ..Default::default()
21617    };
21618    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
21619
21620    cx.set_state(indoc! {"
21621        struct Fˇoo {}
21622    "});
21623
21624    cx.update_editor(|editor, _window, cx| {
21625        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
21626        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
21627        editor.highlight_background::<DocumentHighlightRead>(
21628            &[highlight_range],
21629            |theme| theme.colors().editor_document_highlight_read_background,
21630            cx,
21631        );
21632    });
21633
21634    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
21635        .expect("Prepare rename was not started")
21636        .await
21637        .expect("Prepare rename failed");
21638
21639    let mut rename_handler =
21640        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
21641            let edit = lsp::TextEdit {
21642                range: lsp::Range {
21643                    start: lsp::Position {
21644                        line: 0,
21645                        character: 7,
21646                    },
21647                    end: lsp::Position {
21648                        line: 0,
21649                        character: 10,
21650                    },
21651                },
21652                new_text: "FooRenamed".to_string(),
21653            };
21654            Ok(Some(lsp::WorkspaceEdit::new(
21655                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
21656            )))
21657        });
21658    let rename_task = cx
21659        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
21660        .expect("Confirm rename was not started");
21661    rename_handler.next().await.unwrap();
21662    rename_task.await.expect("Confirm rename failed");
21663    cx.run_until_parked();
21664
21665    // Correct range is renamed, as `surrounding_word` is used to find it.
21666    cx.assert_editor_state(indoc! {"
21667        struct FooRenamedˇ {}
21668    "});
21669}
21670
21671#[gpui::test]
21672async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
21673    init_test(cx, |_| {});
21674    let mut cx = EditorTestContext::new(cx).await;
21675
21676    let language = Arc::new(
21677        Language::new(
21678            LanguageConfig::default(),
21679            Some(tree_sitter_html::LANGUAGE.into()),
21680        )
21681        .with_brackets_query(
21682            r#"
21683            ("<" @open "/>" @close)
21684            ("</" @open ">" @close)
21685            ("<" @open ">" @close)
21686            ("\"" @open "\"" @close)
21687            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
21688        "#,
21689        )
21690        .unwrap(),
21691    );
21692    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
21693
21694    cx.set_state(indoc! {"
21695        <span>ˇ</span>
21696    "});
21697    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21698    cx.assert_editor_state(indoc! {"
21699        <span>
21700        ˇ
21701        </span>
21702    "});
21703
21704    cx.set_state(indoc! {"
21705        <span><span></span>ˇ</span>
21706    "});
21707    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21708    cx.assert_editor_state(indoc! {"
21709        <span><span></span>
21710        ˇ</span>
21711    "});
21712
21713    cx.set_state(indoc! {"
21714        <span>ˇ
21715        </span>
21716    "});
21717    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21718    cx.assert_editor_state(indoc! {"
21719        <span>
21720        ˇ
21721        </span>
21722    "});
21723}
21724
21725#[gpui::test(iterations = 10)]
21726async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
21727    init_test(cx, |_| {});
21728
21729    let fs = FakeFs::new(cx.executor());
21730    fs.insert_tree(
21731        path!("/dir"),
21732        json!({
21733            "a.ts": "a",
21734        }),
21735    )
21736    .await;
21737
21738    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
21739    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21740    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21741
21742    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21743    language_registry.add(Arc::new(Language::new(
21744        LanguageConfig {
21745            name: "TypeScript".into(),
21746            matcher: LanguageMatcher {
21747                path_suffixes: vec!["ts".to_string()],
21748                ..Default::default()
21749            },
21750            ..Default::default()
21751        },
21752        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
21753    )));
21754    let mut fake_language_servers = language_registry.register_fake_lsp(
21755        "TypeScript",
21756        FakeLspAdapter {
21757            capabilities: lsp::ServerCapabilities {
21758                code_lens_provider: Some(lsp::CodeLensOptions {
21759                    resolve_provider: Some(true),
21760                }),
21761                execute_command_provider: Some(lsp::ExecuteCommandOptions {
21762                    commands: vec!["_the/command".to_string()],
21763                    ..lsp::ExecuteCommandOptions::default()
21764                }),
21765                ..lsp::ServerCapabilities::default()
21766            },
21767            ..FakeLspAdapter::default()
21768        },
21769    );
21770
21771    let editor = workspace
21772        .update(cx, |workspace, window, cx| {
21773            workspace.open_abs_path(
21774                PathBuf::from(path!("/dir/a.ts")),
21775                OpenOptions::default(),
21776                window,
21777                cx,
21778            )
21779        })
21780        .unwrap()
21781        .await
21782        .unwrap()
21783        .downcast::<Editor>()
21784        .unwrap();
21785    cx.executor().run_until_parked();
21786
21787    let fake_server = fake_language_servers.next().await.unwrap();
21788
21789    let buffer = editor.update(cx, |editor, cx| {
21790        editor
21791            .buffer()
21792            .read(cx)
21793            .as_singleton()
21794            .expect("have opened a single file by path")
21795    });
21796
21797    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
21798    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
21799    drop(buffer_snapshot);
21800    let actions = cx
21801        .update_window(*workspace, |_, window, cx| {
21802            project.code_actions(&buffer, anchor..anchor, window, cx)
21803        })
21804        .unwrap();
21805
21806    fake_server
21807        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
21808            Ok(Some(vec![
21809                lsp::CodeLens {
21810                    range: lsp::Range::default(),
21811                    command: Some(lsp::Command {
21812                        title: "Code lens command".to_owned(),
21813                        command: "_the/command".to_owned(),
21814                        arguments: None,
21815                    }),
21816                    data: None,
21817                },
21818                lsp::CodeLens {
21819                    range: lsp::Range::default(),
21820                    command: Some(lsp::Command {
21821                        title: "Command not in capabilities".to_owned(),
21822                        command: "not in capabilities".to_owned(),
21823                        arguments: None,
21824                    }),
21825                    data: None,
21826                },
21827                lsp::CodeLens {
21828                    range: lsp::Range {
21829                        start: lsp::Position {
21830                            line: 1,
21831                            character: 1,
21832                        },
21833                        end: lsp::Position {
21834                            line: 1,
21835                            character: 1,
21836                        },
21837                    },
21838                    command: Some(lsp::Command {
21839                        title: "Command not in range".to_owned(),
21840                        command: "_the/command".to_owned(),
21841                        arguments: None,
21842                    }),
21843                    data: None,
21844                },
21845            ]))
21846        })
21847        .next()
21848        .await;
21849
21850    let actions = actions.await.unwrap();
21851    assert_eq!(
21852        actions.len(),
21853        1,
21854        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
21855    );
21856    let action = actions[0].clone();
21857    let apply = project.update(cx, |project, cx| {
21858        project.apply_code_action(buffer.clone(), action, true, cx)
21859    });
21860
21861    // Resolving the code action does not populate its edits. In absence of
21862    // edits, we must execute the given command.
21863    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
21864        |mut lens, _| async move {
21865            let lens_command = lens.command.as_mut().expect("should have a command");
21866            assert_eq!(lens_command.title, "Code lens command");
21867            lens_command.arguments = Some(vec![json!("the-argument")]);
21868            Ok(lens)
21869        },
21870    );
21871
21872    // While executing the command, the language server sends the editor
21873    // a `workspaceEdit` request.
21874    fake_server
21875        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
21876            let fake = fake_server.clone();
21877            move |params, _| {
21878                assert_eq!(params.command, "_the/command");
21879                let fake = fake.clone();
21880                async move {
21881                    fake.server
21882                        .request::<lsp::request::ApplyWorkspaceEdit>(
21883                            lsp::ApplyWorkspaceEditParams {
21884                                label: None,
21885                                edit: lsp::WorkspaceEdit {
21886                                    changes: Some(
21887                                        [(
21888                                            lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
21889                                            vec![lsp::TextEdit {
21890                                                range: lsp::Range::new(
21891                                                    lsp::Position::new(0, 0),
21892                                                    lsp::Position::new(0, 0),
21893                                                ),
21894                                                new_text: "X".into(),
21895                                            }],
21896                                        )]
21897                                        .into_iter()
21898                                        .collect(),
21899                                    ),
21900                                    ..lsp::WorkspaceEdit::default()
21901                                },
21902                            },
21903                        )
21904                        .await
21905                        .into_response()
21906                        .unwrap();
21907                    Ok(Some(json!(null)))
21908                }
21909            }
21910        })
21911        .next()
21912        .await;
21913
21914    // Applying the code lens command returns a project transaction containing the edits
21915    // sent by the language server in its `workspaceEdit` request.
21916    let transaction = apply.await.unwrap();
21917    assert!(transaction.0.contains_key(&buffer));
21918    buffer.update(cx, |buffer, cx| {
21919        assert_eq!(buffer.text(), "Xa");
21920        buffer.undo(cx);
21921        assert_eq!(buffer.text(), "a");
21922    });
21923
21924    let actions_after_edits = cx
21925        .update_window(*workspace, |_, window, cx| {
21926            project.code_actions(&buffer, anchor..anchor, window, cx)
21927        })
21928        .unwrap()
21929        .await
21930        .unwrap();
21931    assert_eq!(
21932        actions, actions_after_edits,
21933        "For the same selection, same code lens actions should be returned"
21934    );
21935
21936    let _responses =
21937        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
21938            panic!("No more code lens requests are expected");
21939        });
21940    editor.update_in(cx, |editor, window, cx| {
21941        editor.select_all(&SelectAll, window, cx);
21942    });
21943    cx.executor().run_until_parked();
21944    let new_actions = cx
21945        .update_window(*workspace, |_, window, cx| {
21946            project.code_actions(&buffer, anchor..anchor, window, cx)
21947        })
21948        .unwrap()
21949        .await
21950        .unwrap();
21951    assert_eq!(
21952        actions, new_actions,
21953        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
21954    );
21955}
21956
21957#[gpui::test]
21958async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
21959    init_test(cx, |_| {});
21960
21961    let fs = FakeFs::new(cx.executor());
21962    let main_text = r#"fn main() {
21963println!("1");
21964println!("2");
21965println!("3");
21966println!("4");
21967println!("5");
21968}"#;
21969    let lib_text = "mod foo {}";
21970    fs.insert_tree(
21971        path!("/a"),
21972        json!({
21973            "lib.rs": lib_text,
21974            "main.rs": main_text,
21975        }),
21976    )
21977    .await;
21978
21979    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21980    let (workspace, cx) =
21981        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21982    let worktree_id = workspace.update(cx, |workspace, cx| {
21983        workspace.project().update(cx, |project, cx| {
21984            project.worktrees(cx).next().unwrap().read(cx).id()
21985        })
21986    });
21987
21988    let expected_ranges = vec![
21989        Point::new(0, 0)..Point::new(0, 0),
21990        Point::new(1, 0)..Point::new(1, 1),
21991        Point::new(2, 0)..Point::new(2, 2),
21992        Point::new(3, 0)..Point::new(3, 3),
21993    ];
21994
21995    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21996    let editor_1 = workspace
21997        .update_in(cx, |workspace, window, cx| {
21998            workspace.open_path(
21999                (worktree_id, "main.rs"),
22000                Some(pane_1.downgrade()),
22001                true,
22002                window,
22003                cx,
22004            )
22005        })
22006        .unwrap()
22007        .await
22008        .downcast::<Editor>()
22009        .unwrap();
22010    pane_1.update(cx, |pane, cx| {
22011        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22012        open_editor.update(cx, |editor, cx| {
22013            assert_eq!(
22014                editor.display_text(cx),
22015                main_text,
22016                "Original main.rs text on initial open",
22017            );
22018            assert_eq!(
22019                editor
22020                    .selections
22021                    .all::<Point>(cx)
22022                    .into_iter()
22023                    .map(|s| s.range())
22024                    .collect::<Vec<_>>(),
22025                vec![Point::zero()..Point::zero()],
22026                "Default selections on initial open",
22027            );
22028        })
22029    });
22030    editor_1.update_in(cx, |editor, window, cx| {
22031        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22032            s.select_ranges(expected_ranges.clone());
22033        });
22034    });
22035
22036    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
22037        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
22038    });
22039    let editor_2 = workspace
22040        .update_in(cx, |workspace, window, cx| {
22041            workspace.open_path(
22042                (worktree_id, "main.rs"),
22043                Some(pane_2.downgrade()),
22044                true,
22045                window,
22046                cx,
22047            )
22048        })
22049        .unwrap()
22050        .await
22051        .downcast::<Editor>()
22052        .unwrap();
22053    pane_2.update(cx, |pane, cx| {
22054        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22055        open_editor.update(cx, |editor, cx| {
22056            assert_eq!(
22057                editor.display_text(cx),
22058                main_text,
22059                "Original main.rs text on initial open in another panel",
22060            );
22061            assert_eq!(
22062                editor
22063                    .selections
22064                    .all::<Point>(cx)
22065                    .into_iter()
22066                    .map(|s| s.range())
22067                    .collect::<Vec<_>>(),
22068                vec![Point::zero()..Point::zero()],
22069                "Default selections on initial open in another panel",
22070            );
22071        })
22072    });
22073
22074    editor_2.update_in(cx, |editor, window, cx| {
22075        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
22076    });
22077
22078    let _other_editor_1 = workspace
22079        .update_in(cx, |workspace, window, cx| {
22080            workspace.open_path(
22081                (worktree_id, "lib.rs"),
22082                Some(pane_1.downgrade()),
22083                true,
22084                window,
22085                cx,
22086            )
22087        })
22088        .unwrap()
22089        .await
22090        .downcast::<Editor>()
22091        .unwrap();
22092    pane_1
22093        .update_in(cx, |pane, window, cx| {
22094            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
22095        })
22096        .await
22097        .unwrap();
22098    drop(editor_1);
22099    pane_1.update(cx, |pane, cx| {
22100        pane.active_item()
22101            .unwrap()
22102            .downcast::<Editor>()
22103            .unwrap()
22104            .update(cx, |editor, cx| {
22105                assert_eq!(
22106                    editor.display_text(cx),
22107                    lib_text,
22108                    "Other file should be open and active",
22109                );
22110            });
22111        assert_eq!(pane.items().count(), 1, "No other editors should be open");
22112    });
22113
22114    let _other_editor_2 = workspace
22115        .update_in(cx, |workspace, window, cx| {
22116            workspace.open_path(
22117                (worktree_id, "lib.rs"),
22118                Some(pane_2.downgrade()),
22119                true,
22120                window,
22121                cx,
22122            )
22123        })
22124        .unwrap()
22125        .await
22126        .downcast::<Editor>()
22127        .unwrap();
22128    pane_2
22129        .update_in(cx, |pane, window, cx| {
22130            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
22131        })
22132        .await
22133        .unwrap();
22134    drop(editor_2);
22135    pane_2.update(cx, |pane, cx| {
22136        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22137        open_editor.update(cx, |editor, cx| {
22138            assert_eq!(
22139                editor.display_text(cx),
22140                lib_text,
22141                "Other file should be open and active in another panel too",
22142            );
22143        });
22144        assert_eq!(
22145            pane.items().count(),
22146            1,
22147            "No other editors should be open in another pane",
22148        );
22149    });
22150
22151    let _editor_1_reopened = workspace
22152        .update_in(cx, |workspace, window, cx| {
22153            workspace.open_path(
22154                (worktree_id, "main.rs"),
22155                Some(pane_1.downgrade()),
22156                true,
22157                window,
22158                cx,
22159            )
22160        })
22161        .unwrap()
22162        .await
22163        .downcast::<Editor>()
22164        .unwrap();
22165    let _editor_2_reopened = workspace
22166        .update_in(cx, |workspace, window, cx| {
22167            workspace.open_path(
22168                (worktree_id, "main.rs"),
22169                Some(pane_2.downgrade()),
22170                true,
22171                window,
22172                cx,
22173            )
22174        })
22175        .unwrap()
22176        .await
22177        .downcast::<Editor>()
22178        .unwrap();
22179    pane_1.update(cx, |pane, cx| {
22180        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22181        open_editor.update(cx, |editor, cx| {
22182            assert_eq!(
22183                editor.display_text(cx),
22184                main_text,
22185                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
22186            );
22187            assert_eq!(
22188                editor
22189                    .selections
22190                    .all::<Point>(cx)
22191                    .into_iter()
22192                    .map(|s| s.range())
22193                    .collect::<Vec<_>>(),
22194                expected_ranges,
22195                "Previous editor in the 1st panel had selections and should get them restored on reopen",
22196            );
22197        })
22198    });
22199    pane_2.update(cx, |pane, cx| {
22200        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22201        open_editor.update(cx, |editor, cx| {
22202            assert_eq!(
22203                editor.display_text(cx),
22204                r#"fn main() {
22205⋯rintln!("1");
22206⋯intln!("2");
22207⋯ntln!("3");
22208println!("4");
22209println!("5");
22210}"#,
22211                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
22212            );
22213            assert_eq!(
22214                editor
22215                    .selections
22216                    .all::<Point>(cx)
22217                    .into_iter()
22218                    .map(|s| s.range())
22219                    .collect::<Vec<_>>(),
22220                vec![Point::zero()..Point::zero()],
22221                "Previous editor in the 2nd pane had no selections changed hence should restore none",
22222            );
22223        })
22224    });
22225}
22226
22227#[gpui::test]
22228async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
22229    init_test(cx, |_| {});
22230
22231    let fs = FakeFs::new(cx.executor());
22232    let main_text = r#"fn main() {
22233println!("1");
22234println!("2");
22235println!("3");
22236println!("4");
22237println!("5");
22238}"#;
22239    let lib_text = "mod foo {}";
22240    fs.insert_tree(
22241        path!("/a"),
22242        json!({
22243            "lib.rs": lib_text,
22244            "main.rs": main_text,
22245        }),
22246    )
22247    .await;
22248
22249    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22250    let (workspace, cx) =
22251        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22252    let worktree_id = workspace.update(cx, |workspace, cx| {
22253        workspace.project().update(cx, |project, cx| {
22254            project.worktrees(cx).next().unwrap().read(cx).id()
22255        })
22256    });
22257
22258    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
22259    let editor = workspace
22260        .update_in(cx, |workspace, window, cx| {
22261            workspace.open_path(
22262                (worktree_id, "main.rs"),
22263                Some(pane.downgrade()),
22264                true,
22265                window,
22266                cx,
22267            )
22268        })
22269        .unwrap()
22270        .await
22271        .downcast::<Editor>()
22272        .unwrap();
22273    pane.update(cx, |pane, cx| {
22274        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22275        open_editor.update(cx, |editor, cx| {
22276            assert_eq!(
22277                editor.display_text(cx),
22278                main_text,
22279                "Original main.rs text on initial open",
22280            );
22281        })
22282    });
22283    editor.update_in(cx, |editor, window, cx| {
22284        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
22285    });
22286
22287    cx.update_global(|store: &mut SettingsStore, cx| {
22288        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
22289            s.restore_on_file_reopen = Some(false);
22290        });
22291    });
22292    editor.update_in(cx, |editor, window, cx| {
22293        editor.fold_ranges(
22294            vec![
22295                Point::new(1, 0)..Point::new(1, 1),
22296                Point::new(2, 0)..Point::new(2, 2),
22297                Point::new(3, 0)..Point::new(3, 3),
22298            ],
22299            false,
22300            window,
22301            cx,
22302        );
22303    });
22304    pane.update_in(cx, |pane, window, cx| {
22305        pane.close_all_items(&CloseAllItems::default(), window, cx)
22306    })
22307    .await
22308    .unwrap();
22309    pane.update(cx, |pane, _| {
22310        assert!(pane.active_item().is_none());
22311    });
22312    cx.update_global(|store: &mut SettingsStore, cx| {
22313        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
22314            s.restore_on_file_reopen = Some(true);
22315        });
22316    });
22317
22318    let _editor_reopened = workspace
22319        .update_in(cx, |workspace, window, cx| {
22320            workspace.open_path(
22321                (worktree_id, "main.rs"),
22322                Some(pane.downgrade()),
22323                true,
22324                window,
22325                cx,
22326            )
22327        })
22328        .unwrap()
22329        .await
22330        .downcast::<Editor>()
22331        .unwrap();
22332    pane.update(cx, |pane, cx| {
22333        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22334        open_editor.update(cx, |editor, cx| {
22335            assert_eq!(
22336                editor.display_text(cx),
22337                main_text,
22338                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
22339            );
22340        })
22341    });
22342}
22343
22344#[gpui::test]
22345async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
22346    struct EmptyModalView {
22347        focus_handle: gpui::FocusHandle,
22348    }
22349    impl EventEmitter<DismissEvent> for EmptyModalView {}
22350    impl Render for EmptyModalView {
22351        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
22352            div()
22353        }
22354    }
22355    impl Focusable for EmptyModalView {
22356        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
22357            self.focus_handle.clone()
22358        }
22359    }
22360    impl workspace::ModalView for EmptyModalView {}
22361    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
22362        EmptyModalView {
22363            focus_handle: cx.focus_handle(),
22364        }
22365    }
22366
22367    init_test(cx, |_| {});
22368
22369    let fs = FakeFs::new(cx.executor());
22370    let project = Project::test(fs, [], cx).await;
22371    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22372    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
22373    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22374    let editor = cx.new_window_entity(|window, cx| {
22375        Editor::new(
22376            EditorMode::full(),
22377            buffer,
22378            Some(project.clone()),
22379            window,
22380            cx,
22381        )
22382    });
22383    workspace
22384        .update(cx, |workspace, window, cx| {
22385            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
22386        })
22387        .unwrap();
22388    editor.update_in(cx, |editor, window, cx| {
22389        editor.open_context_menu(&OpenContextMenu, window, cx);
22390        assert!(editor.mouse_context_menu.is_some());
22391    });
22392    workspace
22393        .update(cx, |workspace, window, cx| {
22394            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
22395        })
22396        .unwrap();
22397    cx.read(|cx| {
22398        assert!(editor.read(cx).mouse_context_menu.is_none());
22399    });
22400}
22401
22402#[gpui::test]
22403async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
22404    init_test(cx, |_| {});
22405
22406    let fs = FakeFs::new(cx.executor());
22407    fs.insert_file(path!("/file.html"), Default::default())
22408        .await;
22409
22410    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
22411
22412    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22413    let html_language = Arc::new(Language::new(
22414        LanguageConfig {
22415            name: "HTML".into(),
22416            matcher: LanguageMatcher {
22417                path_suffixes: vec!["html".to_string()],
22418                ..LanguageMatcher::default()
22419            },
22420            brackets: BracketPairConfig {
22421                pairs: vec![BracketPair {
22422                    start: "<".into(),
22423                    end: ">".into(),
22424                    close: true,
22425                    ..Default::default()
22426                }],
22427                ..Default::default()
22428            },
22429            ..Default::default()
22430        },
22431        Some(tree_sitter_html::LANGUAGE.into()),
22432    ));
22433    language_registry.add(html_language);
22434    let mut fake_servers = language_registry.register_fake_lsp(
22435        "HTML",
22436        FakeLspAdapter {
22437            capabilities: lsp::ServerCapabilities {
22438                completion_provider: Some(lsp::CompletionOptions {
22439                    resolve_provider: Some(true),
22440                    ..Default::default()
22441                }),
22442                ..Default::default()
22443            },
22444            ..Default::default()
22445        },
22446    );
22447
22448    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22449    let cx = &mut VisualTestContext::from_window(*workspace, cx);
22450
22451    let worktree_id = workspace
22452        .update(cx, |workspace, _window, cx| {
22453            workspace.project().update(cx, |project, cx| {
22454                project.worktrees(cx).next().unwrap().read(cx).id()
22455            })
22456        })
22457        .unwrap();
22458    project
22459        .update(cx, |project, cx| {
22460            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
22461        })
22462        .await
22463        .unwrap();
22464    let editor = workspace
22465        .update(cx, |workspace, window, cx| {
22466            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
22467        })
22468        .unwrap()
22469        .await
22470        .unwrap()
22471        .downcast::<Editor>()
22472        .unwrap();
22473
22474    let fake_server = fake_servers.next().await.unwrap();
22475    editor.update_in(cx, |editor, window, cx| {
22476        editor.set_text("<ad></ad>", window, cx);
22477        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22478            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
22479        });
22480        let Some((buffer, _)) = editor
22481            .buffer
22482            .read(cx)
22483            .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
22484        else {
22485            panic!("Failed to get buffer for selection position");
22486        };
22487        let buffer = buffer.read(cx);
22488        let buffer_id = buffer.remote_id();
22489        let opening_range =
22490            buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
22491        let closing_range =
22492            buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
22493        let mut linked_ranges = HashMap::default();
22494        linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
22495        editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
22496    });
22497    let mut completion_handle =
22498        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
22499            Ok(Some(lsp::CompletionResponse::Array(vec![
22500                lsp::CompletionItem {
22501                    label: "head".to_string(),
22502                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22503                        lsp::InsertReplaceEdit {
22504                            new_text: "head".to_string(),
22505                            insert: lsp::Range::new(
22506                                lsp::Position::new(0, 1),
22507                                lsp::Position::new(0, 3),
22508                            ),
22509                            replace: lsp::Range::new(
22510                                lsp::Position::new(0, 1),
22511                                lsp::Position::new(0, 3),
22512                            ),
22513                        },
22514                    )),
22515                    ..Default::default()
22516                },
22517            ])))
22518        });
22519    editor.update_in(cx, |editor, window, cx| {
22520        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
22521    });
22522    cx.run_until_parked();
22523    completion_handle.next().await.unwrap();
22524    editor.update(cx, |editor, _| {
22525        assert!(
22526            editor.context_menu_visible(),
22527            "Completion menu should be visible"
22528        );
22529    });
22530    editor.update_in(cx, |editor, window, cx| {
22531        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
22532    });
22533    cx.executor().run_until_parked();
22534    editor.update(cx, |editor, cx| {
22535        assert_eq!(editor.text(cx), "<head></head>");
22536    });
22537}
22538
22539#[gpui::test]
22540async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
22541    init_test(cx, |_| {});
22542
22543    let fs = FakeFs::new(cx.executor());
22544    fs.insert_tree(
22545        path!("/root"),
22546        json!({
22547            "a": {
22548                "main.rs": "fn main() {}",
22549            },
22550            "foo": {
22551                "bar": {
22552                    "external_file.rs": "pub mod external {}",
22553                }
22554            }
22555        }),
22556    )
22557    .await;
22558
22559    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
22560    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22561    language_registry.add(rust_lang());
22562    let _fake_servers = language_registry.register_fake_lsp(
22563        "Rust",
22564        FakeLspAdapter {
22565            ..FakeLspAdapter::default()
22566        },
22567    );
22568    let (workspace, cx) =
22569        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22570    let worktree_id = workspace.update(cx, |workspace, cx| {
22571        workspace.project().update(cx, |project, cx| {
22572            project.worktrees(cx).next().unwrap().read(cx).id()
22573        })
22574    });
22575
22576    let assert_language_servers_count =
22577        |expected: usize, context: &str, cx: &mut VisualTestContext| {
22578            project.update(cx, |project, cx| {
22579                let current = project
22580                    .lsp_store()
22581                    .read(cx)
22582                    .as_local()
22583                    .unwrap()
22584                    .language_servers
22585                    .len();
22586                assert_eq!(expected, current, "{context}");
22587            });
22588        };
22589
22590    assert_language_servers_count(
22591        0,
22592        "No servers should be running before any file is open",
22593        cx,
22594    );
22595    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
22596    let main_editor = workspace
22597        .update_in(cx, |workspace, window, cx| {
22598            workspace.open_path(
22599                (worktree_id, "main.rs"),
22600                Some(pane.downgrade()),
22601                true,
22602                window,
22603                cx,
22604            )
22605        })
22606        .unwrap()
22607        .await
22608        .downcast::<Editor>()
22609        .unwrap();
22610    pane.update(cx, |pane, cx| {
22611        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22612        open_editor.update(cx, |editor, cx| {
22613            assert_eq!(
22614                editor.display_text(cx),
22615                "fn main() {}",
22616                "Original main.rs text on initial open",
22617            );
22618        });
22619        assert_eq!(open_editor, main_editor);
22620    });
22621    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
22622
22623    let external_editor = workspace
22624        .update_in(cx, |workspace, window, cx| {
22625            workspace.open_abs_path(
22626                PathBuf::from("/root/foo/bar/external_file.rs"),
22627                OpenOptions::default(),
22628                window,
22629                cx,
22630            )
22631        })
22632        .await
22633        .expect("opening external file")
22634        .downcast::<Editor>()
22635        .expect("downcasted external file's open element to editor");
22636    pane.update(cx, |pane, cx| {
22637        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22638        open_editor.update(cx, |editor, cx| {
22639            assert_eq!(
22640                editor.display_text(cx),
22641                "pub mod external {}",
22642                "External file is open now",
22643            );
22644        });
22645        assert_eq!(open_editor, external_editor);
22646    });
22647    assert_language_servers_count(
22648        1,
22649        "Second, external, *.rs file should join the existing server",
22650        cx,
22651    );
22652
22653    pane.update_in(cx, |pane, window, cx| {
22654        pane.close_active_item(&CloseActiveItem::default(), window, cx)
22655    })
22656    .await
22657    .unwrap();
22658    pane.update_in(cx, |pane, window, cx| {
22659        pane.navigate_backward(window, cx);
22660    });
22661    cx.run_until_parked();
22662    pane.update(cx, |pane, cx| {
22663        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22664        open_editor.update(cx, |editor, cx| {
22665            assert_eq!(
22666                editor.display_text(cx),
22667                "pub mod external {}",
22668                "External file is open now",
22669            );
22670        });
22671    });
22672    assert_language_servers_count(
22673        1,
22674        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
22675        cx,
22676    );
22677
22678    cx.update(|_, cx| {
22679        workspace::reload(cx);
22680    });
22681    assert_language_servers_count(
22682        1,
22683        "After reloading the worktree with local and external files opened, only one project should be started",
22684        cx,
22685    );
22686}
22687
22688#[gpui::test]
22689async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
22690    init_test(cx, |_| {});
22691
22692    let mut cx = EditorTestContext::new(cx).await;
22693    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22694    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22695
22696    // test cursor move to start of each line on tab
22697    // for `if`, `elif`, `else`, `while`, `with` and `for`
22698    cx.set_state(indoc! {"
22699        def main():
22700        ˇ    for item in items:
22701        ˇ        while item.active:
22702        ˇ            if item.value > 10:
22703        ˇ                continue
22704        ˇ            elif item.value < 0:
22705        ˇ                break
22706        ˇ            else:
22707        ˇ                with item.context() as ctx:
22708        ˇ                    yield count
22709        ˇ        else:
22710        ˇ            log('while else')
22711        ˇ    else:
22712        ˇ        log('for else')
22713    "});
22714    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22715    cx.assert_editor_state(indoc! {"
22716        def main():
22717            ˇfor item in items:
22718                ˇwhile item.active:
22719                    ˇif item.value > 10:
22720                        ˇcontinue
22721                    ˇelif item.value < 0:
22722                        ˇbreak
22723                    ˇelse:
22724                        ˇwith item.context() as ctx:
22725                            ˇyield count
22726                ˇelse:
22727                    ˇlog('while else')
22728            ˇelse:
22729                ˇlog('for else')
22730    "});
22731    // test relative indent is preserved when tab
22732    // for `if`, `elif`, `else`, `while`, `with` and `for`
22733    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22734    cx.assert_editor_state(indoc! {"
22735        def main():
22736                ˇfor item in items:
22737                    ˇwhile item.active:
22738                        ˇif item.value > 10:
22739                            ˇcontinue
22740                        ˇelif item.value < 0:
22741                            ˇbreak
22742                        ˇelse:
22743                            ˇwith item.context() as ctx:
22744                                ˇyield count
22745                    ˇelse:
22746                        ˇlog('while else')
22747                ˇelse:
22748                    ˇlog('for else')
22749    "});
22750
22751    // test cursor move to start of each line on tab
22752    // for `try`, `except`, `else`, `finally`, `match` and `def`
22753    cx.set_state(indoc! {"
22754        def main():
22755        ˇ    try:
22756        ˇ        fetch()
22757        ˇ    except ValueError:
22758        ˇ        handle_error()
22759        ˇ    else:
22760        ˇ        match value:
22761        ˇ            case _:
22762        ˇ    finally:
22763        ˇ        def status():
22764        ˇ            return 0
22765    "});
22766    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22767    cx.assert_editor_state(indoc! {"
22768        def main():
22769            ˇtry:
22770                ˇfetch()
22771            ˇexcept ValueError:
22772                ˇhandle_error()
22773            ˇelse:
22774                ˇmatch value:
22775                    ˇcase _:
22776            ˇfinally:
22777                ˇdef status():
22778                    ˇreturn 0
22779    "});
22780    // test relative indent is preserved when tab
22781    // for `try`, `except`, `else`, `finally`, `match` and `def`
22782    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22783    cx.assert_editor_state(indoc! {"
22784        def main():
22785                ˇtry:
22786                    ˇfetch()
22787                ˇexcept ValueError:
22788                    ˇhandle_error()
22789                ˇelse:
22790                    ˇmatch value:
22791                        ˇcase _:
22792                ˇfinally:
22793                    ˇdef status():
22794                        ˇreturn 0
22795    "});
22796}
22797
22798#[gpui::test]
22799async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
22800    init_test(cx, |_| {});
22801
22802    let mut cx = EditorTestContext::new(cx).await;
22803    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22804    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22805
22806    // test `else` auto outdents when typed inside `if` block
22807    cx.set_state(indoc! {"
22808        def main():
22809            if i == 2:
22810                return
22811                ˇ
22812    "});
22813    cx.update_editor(|editor, window, cx| {
22814        editor.handle_input("else:", window, cx);
22815    });
22816    cx.assert_editor_state(indoc! {"
22817        def main():
22818            if i == 2:
22819                return
22820            else:ˇ
22821    "});
22822
22823    // test `except` auto outdents when typed inside `try` block
22824    cx.set_state(indoc! {"
22825        def main():
22826            try:
22827                i = 2
22828                ˇ
22829    "});
22830    cx.update_editor(|editor, window, cx| {
22831        editor.handle_input("except:", window, cx);
22832    });
22833    cx.assert_editor_state(indoc! {"
22834        def main():
22835            try:
22836                i = 2
22837            except:ˇ
22838    "});
22839
22840    // test `else` auto outdents when typed inside `except` block
22841    cx.set_state(indoc! {"
22842        def main():
22843            try:
22844                i = 2
22845            except:
22846                j = 2
22847                ˇ
22848    "});
22849    cx.update_editor(|editor, window, cx| {
22850        editor.handle_input("else:", window, cx);
22851    });
22852    cx.assert_editor_state(indoc! {"
22853        def main():
22854            try:
22855                i = 2
22856            except:
22857                j = 2
22858            else:ˇ
22859    "});
22860
22861    // test `finally` auto outdents when typed inside `else` block
22862    cx.set_state(indoc! {"
22863        def main():
22864            try:
22865                i = 2
22866            except:
22867                j = 2
22868            else:
22869                k = 2
22870                ˇ
22871    "});
22872    cx.update_editor(|editor, window, cx| {
22873        editor.handle_input("finally:", window, cx);
22874    });
22875    cx.assert_editor_state(indoc! {"
22876        def main():
22877            try:
22878                i = 2
22879            except:
22880                j = 2
22881            else:
22882                k = 2
22883            finally:ˇ
22884    "});
22885
22886    // test `else` does not outdents when typed inside `except` block right after for block
22887    cx.set_state(indoc! {"
22888        def main():
22889            try:
22890                i = 2
22891            except:
22892                for i in range(n):
22893                    pass
22894                ˇ
22895    "});
22896    cx.update_editor(|editor, window, cx| {
22897        editor.handle_input("else:", window, cx);
22898    });
22899    cx.assert_editor_state(indoc! {"
22900        def main():
22901            try:
22902                i = 2
22903            except:
22904                for i in range(n):
22905                    pass
22906                else:ˇ
22907    "});
22908
22909    // test `finally` auto outdents when typed inside `else` block right after for block
22910    cx.set_state(indoc! {"
22911        def main():
22912            try:
22913                i = 2
22914            except:
22915                j = 2
22916            else:
22917                for i in range(n):
22918                    pass
22919                ˇ
22920    "});
22921    cx.update_editor(|editor, window, cx| {
22922        editor.handle_input("finally:", window, cx);
22923    });
22924    cx.assert_editor_state(indoc! {"
22925        def main():
22926            try:
22927                i = 2
22928            except:
22929                j = 2
22930            else:
22931                for i in range(n):
22932                    pass
22933            finally:ˇ
22934    "});
22935
22936    // test `except` outdents to inner "try" block
22937    cx.set_state(indoc! {"
22938        def main():
22939            try:
22940                i = 2
22941                if i == 2:
22942                    try:
22943                        i = 3
22944                        ˇ
22945    "});
22946    cx.update_editor(|editor, window, cx| {
22947        editor.handle_input("except:", window, cx);
22948    });
22949    cx.assert_editor_state(indoc! {"
22950        def main():
22951            try:
22952                i = 2
22953                if i == 2:
22954                    try:
22955                        i = 3
22956                    except:ˇ
22957    "});
22958
22959    // test `except` outdents to outer "try" block
22960    cx.set_state(indoc! {"
22961        def main():
22962            try:
22963                i = 2
22964                if i == 2:
22965                    try:
22966                        i = 3
22967                ˇ
22968    "});
22969    cx.update_editor(|editor, window, cx| {
22970        editor.handle_input("except:", window, cx);
22971    });
22972    cx.assert_editor_state(indoc! {"
22973        def main():
22974            try:
22975                i = 2
22976                if i == 2:
22977                    try:
22978                        i = 3
22979            except:ˇ
22980    "});
22981
22982    // test `else` stays at correct indent when typed after `for` block
22983    cx.set_state(indoc! {"
22984        def main():
22985            for i in range(10):
22986                if i == 3:
22987                    break
22988            ˇ
22989    "});
22990    cx.update_editor(|editor, window, cx| {
22991        editor.handle_input("else:", window, cx);
22992    });
22993    cx.assert_editor_state(indoc! {"
22994        def main():
22995            for i in range(10):
22996                if i == 3:
22997                    break
22998            else:ˇ
22999    "});
23000
23001    // test does not outdent on typing after line with square brackets
23002    cx.set_state(indoc! {"
23003        def f() -> list[str]:
23004            ˇ
23005    "});
23006    cx.update_editor(|editor, window, cx| {
23007        editor.handle_input("a", window, cx);
23008    });
23009    cx.assert_editor_state(indoc! {"
23010        def f() -> list[str]:
2301123012    "});
23013
23014    // test does not outdent on typing : after case keyword
23015    cx.set_state(indoc! {"
23016        match 1:
23017            caseˇ
23018    "});
23019    cx.update_editor(|editor, window, cx| {
23020        editor.handle_input(":", window, cx);
23021    });
23022    cx.assert_editor_state(indoc! {"
23023        match 1:
23024            case:ˇ
23025    "});
23026}
23027
23028#[gpui::test]
23029async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
23030    init_test(cx, |_| {});
23031    update_test_language_settings(cx, |settings| {
23032        settings.defaults.extend_comment_on_newline = Some(false);
23033    });
23034    let mut cx = EditorTestContext::new(cx).await;
23035    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
23036    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23037
23038    // test correct indent after newline on comment
23039    cx.set_state(indoc! {"
23040        # COMMENT:ˇ
23041    "});
23042    cx.update_editor(|editor, window, cx| {
23043        editor.newline(&Newline, window, cx);
23044    });
23045    cx.assert_editor_state(indoc! {"
23046        # COMMENT:
23047        ˇ
23048    "});
23049
23050    // test correct indent after newline in brackets
23051    cx.set_state(indoc! {"
23052        {ˇ}
23053    "});
23054    cx.update_editor(|editor, window, cx| {
23055        editor.newline(&Newline, window, cx);
23056    });
23057    cx.run_until_parked();
23058    cx.assert_editor_state(indoc! {"
23059        {
23060            ˇ
23061        }
23062    "});
23063
23064    cx.set_state(indoc! {"
23065        (ˇ)
23066    "});
23067    cx.update_editor(|editor, window, cx| {
23068        editor.newline(&Newline, window, cx);
23069    });
23070    cx.run_until_parked();
23071    cx.assert_editor_state(indoc! {"
23072        (
23073            ˇ
23074        )
23075    "});
23076
23077    // do not indent after empty lists or dictionaries
23078    cx.set_state(indoc! {"
23079        a = []ˇ
23080    "});
23081    cx.update_editor(|editor, window, cx| {
23082        editor.newline(&Newline, window, cx);
23083    });
23084    cx.run_until_parked();
23085    cx.assert_editor_state(indoc! {"
23086        a = []
23087        ˇ
23088    "});
23089}
23090
23091#[gpui::test]
23092async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
23093    init_test(cx, |_| {});
23094
23095    let mut cx = EditorTestContext::new(cx).await;
23096    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
23097    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23098
23099    // test cursor move to start of each line on tab
23100    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
23101    cx.set_state(indoc! {"
23102        function main() {
23103        ˇ    for item in $items; do
23104        ˇ        while [ -n \"$item\" ]; do
23105        ˇ            if [ \"$value\" -gt 10 ]; then
23106        ˇ                continue
23107        ˇ            elif [ \"$value\" -lt 0 ]; then
23108        ˇ                break
23109        ˇ            else
23110        ˇ                echo \"$item\"
23111        ˇ            fi
23112        ˇ        done
23113        ˇ    done
23114        ˇ}
23115    "});
23116    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
23117    cx.assert_editor_state(indoc! {"
23118        function main() {
23119            ˇfor item in $items; do
23120                ˇwhile [ -n \"$item\" ]; do
23121                    ˇif [ \"$value\" -gt 10 ]; then
23122                        ˇcontinue
23123                    ˇelif [ \"$value\" -lt 0 ]; then
23124                        ˇbreak
23125                    ˇelse
23126                        ˇecho \"$item\"
23127                    ˇfi
23128                ˇdone
23129            ˇdone
23130        ˇ}
23131    "});
23132    // test relative indent is preserved when tab
23133    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
23134    cx.assert_editor_state(indoc! {"
23135        function main() {
23136                ˇfor item in $items; do
23137                    ˇwhile [ -n \"$item\" ]; do
23138                        ˇif [ \"$value\" -gt 10 ]; then
23139                            ˇcontinue
23140                        ˇelif [ \"$value\" -lt 0 ]; then
23141                            ˇbreak
23142                        ˇelse
23143                            ˇecho \"$item\"
23144                        ˇfi
23145                    ˇdone
23146                ˇdone
23147            ˇ}
23148    "});
23149
23150    // test cursor move to start of each line on tab
23151    // for `case` statement with patterns
23152    cx.set_state(indoc! {"
23153        function handle() {
23154        ˇ    case \"$1\" in
23155        ˇ        start)
23156        ˇ            echo \"a\"
23157        ˇ            ;;
23158        ˇ        stop)
23159        ˇ            echo \"b\"
23160        ˇ            ;;
23161        ˇ        *)
23162        ˇ            echo \"c\"
23163        ˇ            ;;
23164        ˇ    esac
23165        ˇ}
23166    "});
23167    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
23168    cx.assert_editor_state(indoc! {"
23169        function handle() {
23170            ˇcase \"$1\" in
23171                ˇstart)
23172                    ˇecho \"a\"
23173                    ˇ;;
23174                ˇstop)
23175                    ˇecho \"b\"
23176                    ˇ;;
23177                ˇ*)
23178                    ˇecho \"c\"
23179                    ˇ;;
23180            ˇesac
23181        ˇ}
23182    "});
23183}
23184
23185#[gpui::test]
23186async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
23187    init_test(cx, |_| {});
23188
23189    let mut cx = EditorTestContext::new(cx).await;
23190    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
23191    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23192
23193    // test indents on comment insert
23194    cx.set_state(indoc! {"
23195        function main() {
23196        ˇ    for item in $items; do
23197        ˇ        while [ -n \"$item\" ]; do
23198        ˇ            if [ \"$value\" -gt 10 ]; then
23199        ˇ                continue
23200        ˇ            elif [ \"$value\" -lt 0 ]; then
23201        ˇ                break
23202        ˇ            else
23203        ˇ                echo \"$item\"
23204        ˇ            fi
23205        ˇ        done
23206        ˇ    done
23207        ˇ}
23208    "});
23209    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
23210    cx.assert_editor_state(indoc! {"
23211        function main() {
23212        #ˇ    for item in $items; do
23213        #ˇ        while [ -n \"$item\" ]; do
23214        #ˇ            if [ \"$value\" -gt 10 ]; then
23215        #ˇ                continue
23216        #ˇ            elif [ \"$value\" -lt 0 ]; then
23217        #ˇ                break
23218        #ˇ            else
23219        #ˇ                echo \"$item\"
23220        #ˇ            fi
23221        #ˇ        done
23222        #ˇ    done
23223        #ˇ}
23224    "});
23225}
23226
23227#[gpui::test]
23228async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
23229    init_test(cx, |_| {});
23230
23231    let mut cx = EditorTestContext::new(cx).await;
23232    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
23233    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23234
23235    // test `else` auto outdents when typed inside `if` block
23236    cx.set_state(indoc! {"
23237        if [ \"$1\" = \"test\" ]; then
23238            echo \"foo bar\"
23239            ˇ
23240    "});
23241    cx.update_editor(|editor, window, cx| {
23242        editor.handle_input("else", window, cx);
23243    });
23244    cx.assert_editor_state(indoc! {"
23245        if [ \"$1\" = \"test\" ]; then
23246            echo \"foo bar\"
23247        elseˇ
23248    "});
23249
23250    // test `elif` auto outdents when typed inside `if` block
23251    cx.set_state(indoc! {"
23252        if [ \"$1\" = \"test\" ]; then
23253            echo \"foo bar\"
23254            ˇ
23255    "});
23256    cx.update_editor(|editor, window, cx| {
23257        editor.handle_input("elif", window, cx);
23258    });
23259    cx.assert_editor_state(indoc! {"
23260        if [ \"$1\" = \"test\" ]; then
23261            echo \"foo bar\"
23262        elifˇ
23263    "});
23264
23265    // test `fi` auto outdents when typed inside `else` block
23266    cx.set_state(indoc! {"
23267        if [ \"$1\" = \"test\" ]; then
23268            echo \"foo bar\"
23269        else
23270            echo \"bar baz\"
23271            ˇ
23272    "});
23273    cx.update_editor(|editor, window, cx| {
23274        editor.handle_input("fi", window, cx);
23275    });
23276    cx.assert_editor_state(indoc! {"
23277        if [ \"$1\" = \"test\" ]; then
23278            echo \"foo bar\"
23279        else
23280            echo \"bar baz\"
23281        fiˇ
23282    "});
23283
23284    // test `done` auto outdents when typed inside `while` block
23285    cx.set_state(indoc! {"
23286        while read line; do
23287            echo \"$line\"
23288            ˇ
23289    "});
23290    cx.update_editor(|editor, window, cx| {
23291        editor.handle_input("done", window, cx);
23292    });
23293    cx.assert_editor_state(indoc! {"
23294        while read line; do
23295            echo \"$line\"
23296        doneˇ
23297    "});
23298
23299    // test `done` auto outdents when typed inside `for` block
23300    cx.set_state(indoc! {"
23301        for file in *.txt; do
23302            cat \"$file\"
23303            ˇ
23304    "});
23305    cx.update_editor(|editor, window, cx| {
23306        editor.handle_input("done", window, cx);
23307    });
23308    cx.assert_editor_state(indoc! {"
23309        for file in *.txt; do
23310            cat \"$file\"
23311        doneˇ
23312    "});
23313
23314    // test `esac` auto outdents when typed inside `case` block
23315    cx.set_state(indoc! {"
23316        case \"$1\" in
23317            start)
23318                echo \"foo bar\"
23319                ;;
23320            stop)
23321                echo \"bar baz\"
23322                ;;
23323            ˇ
23324    "});
23325    cx.update_editor(|editor, window, cx| {
23326        editor.handle_input("esac", window, cx);
23327    });
23328    cx.assert_editor_state(indoc! {"
23329        case \"$1\" in
23330            start)
23331                echo \"foo bar\"
23332                ;;
23333            stop)
23334                echo \"bar baz\"
23335                ;;
23336        esacˇ
23337    "});
23338
23339    // test `*)` auto outdents when typed inside `case` block
23340    cx.set_state(indoc! {"
23341        case \"$1\" in
23342            start)
23343                echo \"foo bar\"
23344                ;;
23345                ˇ
23346    "});
23347    cx.update_editor(|editor, window, cx| {
23348        editor.handle_input("*)", window, cx);
23349    });
23350    cx.assert_editor_state(indoc! {"
23351        case \"$1\" in
23352            start)
23353                echo \"foo bar\"
23354                ;;
23355            *)ˇ
23356    "});
23357
23358    // test `fi` outdents to correct level with nested if blocks
23359    cx.set_state(indoc! {"
23360        if [ \"$1\" = \"test\" ]; then
23361            echo \"outer if\"
23362            if [ \"$2\" = \"debug\" ]; then
23363                echo \"inner if\"
23364                ˇ
23365    "});
23366    cx.update_editor(|editor, window, cx| {
23367        editor.handle_input("fi", window, cx);
23368    });
23369    cx.assert_editor_state(indoc! {"
23370        if [ \"$1\" = \"test\" ]; then
23371            echo \"outer if\"
23372            if [ \"$2\" = \"debug\" ]; then
23373                echo \"inner if\"
23374            fiˇ
23375    "});
23376}
23377
23378#[gpui::test]
23379async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
23380    init_test(cx, |_| {});
23381    update_test_language_settings(cx, |settings| {
23382        settings.defaults.extend_comment_on_newline = Some(false);
23383    });
23384    let mut cx = EditorTestContext::new(cx).await;
23385    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
23386    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23387
23388    // test correct indent after newline on comment
23389    cx.set_state(indoc! {"
23390        # COMMENT:ˇ
23391    "});
23392    cx.update_editor(|editor, window, cx| {
23393        editor.newline(&Newline, window, cx);
23394    });
23395    cx.assert_editor_state(indoc! {"
23396        # COMMENT:
23397        ˇ
23398    "});
23399
23400    // test correct indent after newline after `then`
23401    cx.set_state(indoc! {"
23402
23403        if [ \"$1\" = \"test\" ]; thenˇ
23404    "});
23405    cx.update_editor(|editor, window, cx| {
23406        editor.newline(&Newline, window, cx);
23407    });
23408    cx.run_until_parked();
23409    cx.assert_editor_state(indoc! {"
23410
23411        if [ \"$1\" = \"test\" ]; then
23412            ˇ
23413    "});
23414
23415    // test correct indent after newline after `else`
23416    cx.set_state(indoc! {"
23417        if [ \"$1\" = \"test\" ]; then
23418        elseˇ
23419    "});
23420    cx.update_editor(|editor, window, cx| {
23421        editor.newline(&Newline, window, cx);
23422    });
23423    cx.run_until_parked();
23424    cx.assert_editor_state(indoc! {"
23425        if [ \"$1\" = \"test\" ]; then
23426        else
23427            ˇ
23428    "});
23429
23430    // test correct indent after newline after `elif`
23431    cx.set_state(indoc! {"
23432        if [ \"$1\" = \"test\" ]; then
23433        elifˇ
23434    "});
23435    cx.update_editor(|editor, window, cx| {
23436        editor.newline(&Newline, window, cx);
23437    });
23438    cx.run_until_parked();
23439    cx.assert_editor_state(indoc! {"
23440        if [ \"$1\" = \"test\" ]; then
23441        elif
23442            ˇ
23443    "});
23444
23445    // test correct indent after newline after `do`
23446    cx.set_state(indoc! {"
23447        for file in *.txt; doˇ
23448    "});
23449    cx.update_editor(|editor, window, cx| {
23450        editor.newline(&Newline, window, cx);
23451    });
23452    cx.run_until_parked();
23453    cx.assert_editor_state(indoc! {"
23454        for file in *.txt; do
23455            ˇ
23456    "});
23457
23458    // test correct indent after newline after case pattern
23459    cx.set_state(indoc! {"
23460        case \"$1\" in
23461            start)ˇ
23462    "});
23463    cx.update_editor(|editor, window, cx| {
23464        editor.newline(&Newline, window, cx);
23465    });
23466    cx.run_until_parked();
23467    cx.assert_editor_state(indoc! {"
23468        case \"$1\" in
23469            start)
23470                ˇ
23471    "});
23472
23473    // test correct indent after newline after case pattern
23474    cx.set_state(indoc! {"
23475        case \"$1\" in
23476            start)
23477                ;;
23478            *)ˇ
23479    "});
23480    cx.update_editor(|editor, window, cx| {
23481        editor.newline(&Newline, window, cx);
23482    });
23483    cx.run_until_parked();
23484    cx.assert_editor_state(indoc! {"
23485        case \"$1\" in
23486            start)
23487                ;;
23488            *)
23489                ˇ
23490    "});
23491
23492    // test correct indent after newline after function opening brace
23493    cx.set_state(indoc! {"
23494        function test() {ˇ}
23495    "});
23496    cx.update_editor(|editor, window, cx| {
23497        editor.newline(&Newline, window, cx);
23498    });
23499    cx.run_until_parked();
23500    cx.assert_editor_state(indoc! {"
23501        function test() {
23502            ˇ
23503        }
23504    "});
23505
23506    // test no extra indent after semicolon on same line
23507    cx.set_state(indoc! {"
23508        echo \"test\"23509    "});
23510    cx.update_editor(|editor, window, cx| {
23511        editor.newline(&Newline, window, cx);
23512    });
23513    cx.run_until_parked();
23514    cx.assert_editor_state(indoc! {"
23515        echo \"test\";
23516        ˇ
23517    "});
23518}
23519
23520fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
23521    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
23522    point..point
23523}
23524
23525#[track_caller]
23526fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
23527    let (text, ranges) = marked_text_ranges(marked_text, true);
23528    assert_eq!(editor.text(cx), text);
23529    assert_eq!(
23530        editor.selections.ranges(cx),
23531        ranges,
23532        "Assert selections are {}",
23533        marked_text
23534    );
23535}
23536
23537pub fn handle_signature_help_request(
23538    cx: &mut EditorLspTestContext,
23539    mocked_response: lsp::SignatureHelp,
23540) -> impl Future<Output = ()> + use<> {
23541    let mut request =
23542        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
23543            let mocked_response = mocked_response.clone();
23544            async move { Ok(Some(mocked_response)) }
23545        });
23546
23547    async move {
23548        request.next().await;
23549    }
23550}
23551
23552#[track_caller]
23553pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
23554    cx.update_editor(|editor, _, _| {
23555        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
23556            let entries = menu.entries.borrow();
23557            let entries = entries
23558                .iter()
23559                .map(|entry| entry.string.as_str())
23560                .collect::<Vec<_>>();
23561            assert_eq!(entries, expected);
23562        } else {
23563            panic!("Expected completions menu");
23564        }
23565    });
23566}
23567
23568/// Handle completion request passing a marked string specifying where the completion
23569/// should be triggered from using '|' character, what range should be replaced, and what completions
23570/// should be returned using '<' and '>' to delimit the range.
23571///
23572/// Also see `handle_completion_request_with_insert_and_replace`.
23573#[track_caller]
23574pub fn handle_completion_request(
23575    marked_string: &str,
23576    completions: Vec<&'static str>,
23577    is_incomplete: bool,
23578    counter: Arc<AtomicUsize>,
23579    cx: &mut EditorLspTestContext,
23580) -> impl Future<Output = ()> {
23581    let complete_from_marker: TextRangeMarker = '|'.into();
23582    let replace_range_marker: TextRangeMarker = ('<', '>').into();
23583    let (_, mut marked_ranges) = marked_text_ranges_by(
23584        marked_string,
23585        vec![complete_from_marker.clone(), replace_range_marker.clone()],
23586    );
23587
23588    let complete_from_position =
23589        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
23590    let replace_range =
23591        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
23592
23593    let mut request =
23594        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
23595            let completions = completions.clone();
23596            counter.fetch_add(1, atomic::Ordering::Release);
23597            async move {
23598                assert_eq!(params.text_document_position.text_document.uri, url.clone());
23599                assert_eq!(
23600                    params.text_document_position.position,
23601                    complete_from_position
23602                );
23603                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
23604                    is_incomplete,
23605                    item_defaults: None,
23606                    items: completions
23607                        .iter()
23608                        .map(|completion_text| lsp::CompletionItem {
23609                            label: completion_text.to_string(),
23610                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
23611                                range: replace_range,
23612                                new_text: completion_text.to_string(),
23613                            })),
23614                            ..Default::default()
23615                        })
23616                        .collect(),
23617                })))
23618            }
23619        });
23620
23621    async move {
23622        request.next().await;
23623    }
23624}
23625
23626/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
23627/// given instead, which also contains an `insert` range.
23628///
23629/// This function uses markers to define ranges:
23630/// - `|` marks the cursor position
23631/// - `<>` marks the replace range
23632/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
23633pub fn handle_completion_request_with_insert_and_replace(
23634    cx: &mut EditorLspTestContext,
23635    marked_string: &str,
23636    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
23637    counter: Arc<AtomicUsize>,
23638) -> impl Future<Output = ()> {
23639    let complete_from_marker: TextRangeMarker = '|'.into();
23640    let replace_range_marker: TextRangeMarker = ('<', '>').into();
23641    let insert_range_marker: TextRangeMarker = ('{', '}').into();
23642
23643    let (_, mut marked_ranges) = marked_text_ranges_by(
23644        marked_string,
23645        vec![
23646            complete_from_marker.clone(),
23647            replace_range_marker.clone(),
23648            insert_range_marker.clone(),
23649        ],
23650    );
23651
23652    let complete_from_position =
23653        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
23654    let replace_range =
23655        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
23656
23657    let insert_range = match marked_ranges.remove(&insert_range_marker) {
23658        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
23659        _ => lsp::Range {
23660            start: replace_range.start,
23661            end: complete_from_position,
23662        },
23663    };
23664
23665    let mut request =
23666        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
23667            let completions = completions.clone();
23668            counter.fetch_add(1, atomic::Ordering::Release);
23669            async move {
23670                assert_eq!(params.text_document_position.text_document.uri, url.clone());
23671                assert_eq!(
23672                    params.text_document_position.position, complete_from_position,
23673                    "marker `|` position doesn't match",
23674                );
23675                Ok(Some(lsp::CompletionResponse::Array(
23676                    completions
23677                        .iter()
23678                        .map(|(label, new_text)| lsp::CompletionItem {
23679                            label: label.to_string(),
23680                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23681                                lsp::InsertReplaceEdit {
23682                                    insert: insert_range,
23683                                    replace: replace_range,
23684                                    new_text: new_text.to_string(),
23685                                },
23686                            )),
23687                            ..Default::default()
23688                        })
23689                        .collect(),
23690                )))
23691            }
23692        });
23693
23694    async move {
23695        request.next().await;
23696    }
23697}
23698
23699fn handle_resolve_completion_request(
23700    cx: &mut EditorLspTestContext,
23701    edits: Option<Vec<(&'static str, &'static str)>>,
23702) -> impl Future<Output = ()> {
23703    let edits = edits.map(|edits| {
23704        edits
23705            .iter()
23706            .map(|(marked_string, new_text)| {
23707                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
23708                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
23709                lsp::TextEdit::new(replace_range, new_text.to_string())
23710            })
23711            .collect::<Vec<_>>()
23712    });
23713
23714    let mut request =
23715        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
23716            let edits = edits.clone();
23717            async move {
23718                Ok(lsp::CompletionItem {
23719                    additional_text_edits: edits,
23720                    ..Default::default()
23721                })
23722            }
23723        });
23724
23725    async move {
23726        request.next().await;
23727    }
23728}
23729
23730pub(crate) fn update_test_language_settings(
23731    cx: &mut TestAppContext,
23732    f: impl Fn(&mut AllLanguageSettingsContent),
23733) {
23734    cx.update(|cx| {
23735        SettingsStore::update_global(cx, |store, cx| {
23736            store.update_user_settings::<AllLanguageSettings>(cx, f);
23737        });
23738    });
23739}
23740
23741pub(crate) fn update_test_project_settings(
23742    cx: &mut TestAppContext,
23743    f: impl Fn(&mut ProjectSettings),
23744) {
23745    cx.update(|cx| {
23746        SettingsStore::update_global(cx, |store, cx| {
23747            store.update_user_settings::<ProjectSettings>(cx, f);
23748        });
23749    });
23750}
23751
23752pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
23753    cx.update(|cx| {
23754        assets::Assets.load_test_fonts(cx);
23755        let store = SettingsStore::test(cx);
23756        cx.set_global(store);
23757        theme::init(theme::LoadThemes::JustBase, cx);
23758        release_channel::init(SemanticVersion::default(), cx);
23759        client::init_settings(cx);
23760        language::init(cx);
23761        Project::init_settings(cx);
23762        workspace::init_settings(cx);
23763        crate::init(cx);
23764    });
23765    zlog::init_test();
23766    update_test_language_settings(cx, f);
23767}
23768
23769#[track_caller]
23770fn assert_hunk_revert(
23771    not_reverted_text_with_selections: &str,
23772    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
23773    expected_reverted_text_with_selections: &str,
23774    base_text: &str,
23775    cx: &mut EditorLspTestContext,
23776) {
23777    cx.set_state(not_reverted_text_with_selections);
23778    cx.set_head_text(base_text);
23779    cx.executor().run_until_parked();
23780
23781    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
23782        let snapshot = editor.snapshot(window, cx);
23783        let reverted_hunk_statuses = snapshot
23784            .buffer_snapshot
23785            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
23786            .map(|hunk| hunk.status().kind)
23787            .collect::<Vec<_>>();
23788
23789        editor.git_restore(&Default::default(), window, cx);
23790        reverted_hunk_statuses
23791    });
23792    cx.executor().run_until_parked();
23793    cx.assert_editor_state(expected_reverted_text_with_selections);
23794    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
23795}
23796
23797#[gpui::test(iterations = 10)]
23798async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
23799    init_test(cx, |_| {});
23800
23801    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
23802    let counter = diagnostic_requests.clone();
23803
23804    let fs = FakeFs::new(cx.executor());
23805    fs.insert_tree(
23806        path!("/a"),
23807        json!({
23808            "first.rs": "fn main() { let a = 5; }",
23809            "second.rs": "// Test file",
23810        }),
23811    )
23812    .await;
23813
23814    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23815    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23816    let cx = &mut VisualTestContext::from_window(*workspace, cx);
23817
23818    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23819    language_registry.add(rust_lang());
23820    let mut fake_servers = language_registry.register_fake_lsp(
23821        "Rust",
23822        FakeLspAdapter {
23823            capabilities: lsp::ServerCapabilities {
23824                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
23825                    lsp::DiagnosticOptions {
23826                        identifier: None,
23827                        inter_file_dependencies: true,
23828                        workspace_diagnostics: true,
23829                        work_done_progress_options: Default::default(),
23830                    },
23831                )),
23832                ..Default::default()
23833            },
23834            ..Default::default()
23835        },
23836    );
23837
23838    let editor = workspace
23839        .update(cx, |workspace, window, cx| {
23840            workspace.open_abs_path(
23841                PathBuf::from(path!("/a/first.rs")),
23842                OpenOptions::default(),
23843                window,
23844                cx,
23845            )
23846        })
23847        .unwrap()
23848        .await
23849        .unwrap()
23850        .downcast::<Editor>()
23851        .unwrap();
23852    let fake_server = fake_servers.next().await.unwrap();
23853    let server_id = fake_server.server.server_id();
23854    let mut first_request = fake_server
23855        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
23856            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
23857            let result_id = Some(new_result_id.to_string());
23858            assert_eq!(
23859                params.text_document.uri,
23860                lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
23861            );
23862            async move {
23863                Ok(lsp::DocumentDiagnosticReportResult::Report(
23864                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
23865                        related_documents: None,
23866                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
23867                            items: Vec::new(),
23868                            result_id,
23869                        },
23870                    }),
23871                ))
23872            }
23873        });
23874
23875    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
23876        project.update(cx, |project, cx| {
23877            let buffer_id = editor
23878                .read(cx)
23879                .buffer()
23880                .read(cx)
23881                .as_singleton()
23882                .expect("created a singleton buffer")
23883                .read(cx)
23884                .remote_id();
23885            let buffer_result_id = project
23886                .lsp_store()
23887                .read(cx)
23888                .result_id(server_id, buffer_id, cx);
23889            assert_eq!(expected, buffer_result_id);
23890        });
23891    };
23892
23893    ensure_result_id(None, cx);
23894    cx.executor().advance_clock(Duration::from_millis(60));
23895    cx.executor().run_until_parked();
23896    assert_eq!(
23897        diagnostic_requests.load(atomic::Ordering::Acquire),
23898        1,
23899        "Opening file should trigger diagnostic request"
23900    );
23901    first_request
23902        .next()
23903        .await
23904        .expect("should have sent the first diagnostics pull request");
23905    ensure_result_id(Some("1".to_string()), cx);
23906
23907    // Editing should trigger diagnostics
23908    editor.update_in(cx, |editor, window, cx| {
23909        editor.handle_input("2", window, cx)
23910    });
23911    cx.executor().advance_clock(Duration::from_millis(60));
23912    cx.executor().run_until_parked();
23913    assert_eq!(
23914        diagnostic_requests.load(atomic::Ordering::Acquire),
23915        2,
23916        "Editing should trigger diagnostic request"
23917    );
23918    ensure_result_id(Some("2".to_string()), cx);
23919
23920    // Moving cursor should not trigger diagnostic request
23921    editor.update_in(cx, |editor, window, cx| {
23922        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23923            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
23924        });
23925    });
23926    cx.executor().advance_clock(Duration::from_millis(60));
23927    cx.executor().run_until_parked();
23928    assert_eq!(
23929        diagnostic_requests.load(atomic::Ordering::Acquire),
23930        2,
23931        "Cursor movement should not trigger diagnostic request"
23932    );
23933    ensure_result_id(Some("2".to_string()), cx);
23934    // Multiple rapid edits should be debounced
23935    for _ in 0..5 {
23936        editor.update_in(cx, |editor, window, cx| {
23937            editor.handle_input("x", window, cx)
23938        });
23939    }
23940    cx.executor().advance_clock(Duration::from_millis(60));
23941    cx.executor().run_until_parked();
23942
23943    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
23944    assert!(
23945        final_requests <= 4,
23946        "Multiple rapid edits should be debounced (got {final_requests} requests)",
23947    );
23948    ensure_result_id(Some(final_requests.to_string()), cx);
23949}
23950
23951#[gpui::test]
23952async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
23953    // Regression test for issue #11671
23954    // Previously, adding a cursor after moving multiple cursors would reset
23955    // the cursor count instead of adding to the existing cursors.
23956    init_test(cx, |_| {});
23957    let mut cx = EditorTestContext::new(cx).await;
23958
23959    // Create a simple buffer with cursor at start
23960    cx.set_state(indoc! {"
23961        ˇaaaa
23962        bbbb
23963        cccc
23964        dddd
23965        eeee
23966        ffff
23967        gggg
23968        hhhh"});
23969
23970    // Add 2 cursors below (so we have 3 total)
23971    cx.update_editor(|editor, window, cx| {
23972        editor.add_selection_below(&Default::default(), window, cx);
23973        editor.add_selection_below(&Default::default(), window, cx);
23974    });
23975
23976    // Verify we have 3 cursors
23977    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
23978    assert_eq!(
23979        initial_count, 3,
23980        "Should have 3 cursors after adding 2 below"
23981    );
23982
23983    // Move down one line
23984    cx.update_editor(|editor, window, cx| {
23985        editor.move_down(&MoveDown, window, cx);
23986    });
23987
23988    // Add another cursor below
23989    cx.update_editor(|editor, window, cx| {
23990        editor.add_selection_below(&Default::default(), window, cx);
23991    });
23992
23993    // Should now have 4 cursors (3 original + 1 new)
23994    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
23995    assert_eq!(
23996        final_count, 4,
23997        "Should have 4 cursors after moving and adding another"
23998    );
23999}
24000
24001#[gpui::test(iterations = 10)]
24002async fn test_document_colors(cx: &mut TestAppContext) {
24003    let expected_color = Rgba {
24004        r: 0.33,
24005        g: 0.33,
24006        b: 0.33,
24007        a: 0.33,
24008    };
24009
24010    init_test(cx, |_| {});
24011
24012    let fs = FakeFs::new(cx.executor());
24013    fs.insert_tree(
24014        path!("/a"),
24015        json!({
24016            "first.rs": "fn main() { let a = 5; }",
24017        }),
24018    )
24019    .await;
24020
24021    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24022    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24023    let cx = &mut VisualTestContext::from_window(*workspace, cx);
24024
24025    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24026    language_registry.add(rust_lang());
24027    let mut fake_servers = language_registry.register_fake_lsp(
24028        "Rust",
24029        FakeLspAdapter {
24030            capabilities: lsp::ServerCapabilities {
24031                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
24032                ..lsp::ServerCapabilities::default()
24033            },
24034            name: "rust-analyzer",
24035            ..FakeLspAdapter::default()
24036        },
24037    );
24038    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
24039        "Rust",
24040        FakeLspAdapter {
24041            capabilities: lsp::ServerCapabilities {
24042                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
24043                ..lsp::ServerCapabilities::default()
24044            },
24045            name: "not-rust-analyzer",
24046            ..FakeLspAdapter::default()
24047        },
24048    );
24049
24050    let editor = workspace
24051        .update(cx, |workspace, window, cx| {
24052            workspace.open_abs_path(
24053                PathBuf::from(path!("/a/first.rs")),
24054                OpenOptions::default(),
24055                window,
24056                cx,
24057            )
24058        })
24059        .unwrap()
24060        .await
24061        .unwrap()
24062        .downcast::<Editor>()
24063        .unwrap();
24064    let fake_language_server = fake_servers.next().await.unwrap();
24065    let fake_language_server_without_capabilities =
24066        fake_servers_without_capabilities.next().await.unwrap();
24067    let requests_made = Arc::new(AtomicUsize::new(0));
24068    let closure_requests_made = Arc::clone(&requests_made);
24069    let mut color_request_handle = fake_language_server
24070        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
24071            let requests_made = Arc::clone(&closure_requests_made);
24072            async move {
24073                assert_eq!(
24074                    params.text_document.uri,
24075                    lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
24076                );
24077                requests_made.fetch_add(1, atomic::Ordering::Release);
24078                Ok(vec![
24079                    lsp::ColorInformation {
24080                        range: lsp::Range {
24081                            start: lsp::Position {
24082                                line: 0,
24083                                character: 0,
24084                            },
24085                            end: lsp::Position {
24086                                line: 0,
24087                                character: 1,
24088                            },
24089                        },
24090                        color: lsp::Color {
24091                            red: 0.33,
24092                            green: 0.33,
24093                            blue: 0.33,
24094                            alpha: 0.33,
24095                        },
24096                    },
24097                    lsp::ColorInformation {
24098                        range: lsp::Range {
24099                            start: lsp::Position {
24100                                line: 0,
24101                                character: 0,
24102                            },
24103                            end: lsp::Position {
24104                                line: 0,
24105                                character: 1,
24106                            },
24107                        },
24108                        color: lsp::Color {
24109                            red: 0.33,
24110                            green: 0.33,
24111                            blue: 0.33,
24112                            alpha: 0.33,
24113                        },
24114                    },
24115                ])
24116            }
24117        });
24118
24119    let _handle = fake_language_server_without_capabilities
24120        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
24121            panic!("Should not be called");
24122        });
24123    cx.executor().advance_clock(Duration::from_millis(100));
24124    color_request_handle.next().await.unwrap();
24125    cx.run_until_parked();
24126    assert_eq!(
24127        1,
24128        requests_made.load(atomic::Ordering::Acquire),
24129        "Should query for colors once per editor open"
24130    );
24131    editor.update_in(cx, |editor, _, cx| {
24132        assert_eq!(
24133            vec![expected_color],
24134            extract_color_inlays(editor, cx),
24135            "Should have an initial inlay"
24136        );
24137    });
24138
24139    // opening another file in a split should not influence the LSP query counter
24140    workspace
24141        .update(cx, |workspace, window, cx| {
24142            assert_eq!(
24143                workspace.panes().len(),
24144                1,
24145                "Should have one pane with one editor"
24146            );
24147            workspace.move_item_to_pane_in_direction(
24148                &MoveItemToPaneInDirection {
24149                    direction: SplitDirection::Right,
24150                    focus: false,
24151                    clone: true,
24152                },
24153                window,
24154                cx,
24155            );
24156        })
24157        .unwrap();
24158    cx.run_until_parked();
24159    workspace
24160        .update(cx, |workspace, _, cx| {
24161            let panes = workspace.panes();
24162            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
24163            for pane in panes {
24164                let editor = pane
24165                    .read(cx)
24166                    .active_item()
24167                    .and_then(|item| item.downcast::<Editor>())
24168                    .expect("Should have opened an editor in each split");
24169                let editor_file = editor
24170                    .read(cx)
24171                    .buffer()
24172                    .read(cx)
24173                    .as_singleton()
24174                    .expect("test deals with singleton buffers")
24175                    .read(cx)
24176                    .file()
24177                    .expect("test buffese should have a file")
24178                    .path();
24179                assert_eq!(
24180                    editor_file.as_ref(),
24181                    Path::new("first.rs"),
24182                    "Both editors should be opened for the same file"
24183                )
24184            }
24185        })
24186        .unwrap();
24187
24188    cx.executor().advance_clock(Duration::from_millis(500));
24189    let save = editor.update_in(cx, |editor, window, cx| {
24190        editor.move_to_end(&MoveToEnd, window, cx);
24191        editor.handle_input("dirty", window, cx);
24192        editor.save(
24193            SaveOptions {
24194                format: true,
24195                autosave: true,
24196            },
24197            project.clone(),
24198            window,
24199            cx,
24200        )
24201    });
24202    save.await.unwrap();
24203
24204    color_request_handle.next().await.unwrap();
24205    cx.run_until_parked();
24206    assert_eq!(
24207        3,
24208        requests_made.load(atomic::Ordering::Acquire),
24209        "Should query for colors once per save and once per formatting after save"
24210    );
24211
24212    drop(editor);
24213    let close = workspace
24214        .update(cx, |workspace, window, cx| {
24215            workspace.active_pane().update(cx, |pane, cx| {
24216                pane.close_active_item(&CloseActiveItem::default(), window, cx)
24217            })
24218        })
24219        .unwrap();
24220    close.await.unwrap();
24221    let close = workspace
24222        .update(cx, |workspace, window, cx| {
24223            workspace.active_pane().update(cx, |pane, cx| {
24224                pane.close_active_item(&CloseActiveItem::default(), window, cx)
24225            })
24226        })
24227        .unwrap();
24228    close.await.unwrap();
24229    assert_eq!(
24230        3,
24231        requests_made.load(atomic::Ordering::Acquire),
24232        "After saving and closing all editors, no extra requests should be made"
24233    );
24234    workspace
24235        .update(cx, |workspace, _, cx| {
24236            assert!(
24237                workspace.active_item(cx).is_none(),
24238                "Should close all editors"
24239            )
24240        })
24241        .unwrap();
24242
24243    workspace
24244        .update(cx, |workspace, window, cx| {
24245            workspace.active_pane().update(cx, |pane, cx| {
24246                pane.navigate_backward(window, cx);
24247            })
24248        })
24249        .unwrap();
24250    cx.executor().advance_clock(Duration::from_millis(100));
24251    cx.run_until_parked();
24252    let editor = workspace
24253        .update(cx, |workspace, _, cx| {
24254            workspace
24255                .active_item(cx)
24256                .expect("Should have reopened the editor again after navigating back")
24257                .downcast::<Editor>()
24258                .expect("Should be an editor")
24259        })
24260        .unwrap();
24261    color_request_handle.next().await.unwrap();
24262    assert_eq!(
24263        3,
24264        requests_made.load(atomic::Ordering::Acquire),
24265        "Cache should be reused on buffer close and reopen"
24266    );
24267    editor.update(cx, |editor, cx| {
24268        assert_eq!(
24269            vec![expected_color],
24270            extract_color_inlays(editor, cx),
24271            "Should have an initial inlay"
24272        );
24273    });
24274}
24275
24276#[gpui::test]
24277async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
24278    init_test(cx, |_| {});
24279    let (editor, cx) = cx.add_window_view(Editor::single_line);
24280    editor.update_in(cx, |editor, window, cx| {
24281        editor.set_text("oops\n\nwow\n", window, cx)
24282    });
24283    cx.run_until_parked();
24284    editor.update(cx, |editor, cx| {
24285        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
24286    });
24287    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
24288    cx.run_until_parked();
24289    editor.update(cx, |editor, cx| {
24290        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
24291    });
24292}
24293
24294#[track_caller]
24295fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
24296    editor
24297        .all_inlays(cx)
24298        .into_iter()
24299        .filter_map(|inlay| inlay.get_color())
24300        .map(Rgba::from)
24301        .collect()
24302}