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 collections::HashMap;
   17use futures::{StreamExt, channel::oneshot};
   18use gpui::{
   19    BackgroundExecutor, DismissEvent, Rgba, SemanticVersion, TestAppContext, UpdateGlobal,
   20    VisualTestContext, WindowBounds, WindowOptions, div,
   21};
   22use indoc::indoc;
   23use language::{
   24    BracketPairConfig,
   25    Capability::ReadWrite,
   26    DiagnosticSourceKind, FakeLspAdapter, IndentGuideSettings, LanguageConfig,
   27    LanguageConfigOverride, LanguageMatcher, LanguageName, Override, Point,
   28    language_settings::{
   29        CompletionSettingsContent, FormatterList, LanguageSettingsContent, LspInsertMode,
   30    },
   31    tree_sitter_python,
   32};
   33use language_settings::Formatter;
   34use languages::rust_lang;
   35use lsp::CompletionParams;
   36use multi_buffer::{IndentGuide, PathKey};
   37use parking_lot::Mutex;
   38use pretty_assertions::{assert_eq, assert_ne};
   39use project::{
   40    FakeFs,
   41    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   42    project_settings::LspSettings,
   43};
   44use serde_json::{self, json};
   45use settings::{
   46    AllLanguageSettingsContent, IndentGuideBackgroundColoring, IndentGuideColoring,
   47    ProjectSettingsContent,
   48};
   49use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   50use std::{
   51    iter,
   52    sync::atomic::{self, AtomicUsize},
   53};
   54use test::build_editor_with_project;
   55use text::ToPoint as _;
   56use unindent::Unindent;
   57use util::{
   58    assert_set_eq, path,
   59    rel_path::rel_path,
   60    test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
   61    uri,
   62};
   63use workspace::{
   64    CloseActiveItem, CloseAllItems, CloseOtherItems, MoveItemToPaneInDirection, NavigationEntry,
   65    OpenOptions, ViewId,
   66    invalid_item_view::InvalidItemView,
   67    item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions},
   68    register_project_item,
   69};
   70
   71fn display_ranges(editor: &Editor, cx: &mut Context<'_, Editor>) -> Vec<Range<DisplayPoint>> {
   72    editor
   73        .selections
   74        .display_ranges(&editor.display_snapshot(cx))
   75}
   76
   77#[gpui::test]
   78fn test_edit_events(cx: &mut TestAppContext) {
   79    init_test(cx, |_| {});
   80
   81    let buffer = cx.new(|cx| {
   82        let mut buffer = language::Buffer::local("123456", cx);
   83        buffer.set_group_interval(Duration::from_secs(1));
   84        buffer
   85    });
   86
   87    let events = Rc::new(RefCell::new(Vec::new()));
   88    let editor1 = cx.add_window({
   89        let events = events.clone();
   90        |window, cx| {
   91            let entity = cx.entity();
   92            cx.subscribe_in(
   93                &entity,
   94                window,
   95                move |_, _, event: &EditorEvent, _, _| match event {
   96                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
   97                    EditorEvent::BufferEdited => {
   98                        events.borrow_mut().push(("editor1", "buffer edited"))
   99                    }
  100                    _ => {}
  101                },
  102            )
  103            .detach();
  104            Editor::for_buffer(buffer.clone(), None, window, cx)
  105        }
  106    });
  107
  108    let editor2 = cx.add_window({
  109        let events = events.clone();
  110        |window, cx| {
  111            cx.subscribe_in(
  112                &cx.entity(),
  113                window,
  114                move |_, _, event: &EditorEvent, _, _| match event {
  115                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
  116                    EditorEvent::BufferEdited => {
  117                        events.borrow_mut().push(("editor2", "buffer edited"))
  118                    }
  119                    _ => {}
  120                },
  121            )
  122            .detach();
  123            Editor::for_buffer(buffer.clone(), None, window, cx)
  124        }
  125    });
  126
  127    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  128
  129    // Mutating editor 1 will emit an `Edited` event only for that editor.
  130    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  131    assert_eq!(
  132        mem::take(&mut *events.borrow_mut()),
  133        [
  134            ("editor1", "edited"),
  135            ("editor1", "buffer edited"),
  136            ("editor2", "buffer edited"),
  137        ]
  138    );
  139
  140    // Mutating editor 2 will emit an `Edited` event only for that editor.
  141    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  142    assert_eq!(
  143        mem::take(&mut *events.borrow_mut()),
  144        [
  145            ("editor2", "edited"),
  146            ("editor1", "buffer edited"),
  147            ("editor2", "buffer edited"),
  148        ]
  149    );
  150
  151    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  152    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  153    assert_eq!(
  154        mem::take(&mut *events.borrow_mut()),
  155        [
  156            ("editor1", "edited"),
  157            ("editor1", "buffer edited"),
  158            ("editor2", "buffer edited"),
  159        ]
  160    );
  161
  162    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  163    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  164    assert_eq!(
  165        mem::take(&mut *events.borrow_mut()),
  166        [
  167            ("editor1", "edited"),
  168            ("editor1", "buffer edited"),
  169            ("editor2", "buffer edited"),
  170        ]
  171    );
  172
  173    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  174    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  175    assert_eq!(
  176        mem::take(&mut *events.borrow_mut()),
  177        [
  178            ("editor2", "edited"),
  179            ("editor1", "buffer edited"),
  180            ("editor2", "buffer edited"),
  181        ]
  182    );
  183
  184    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  185    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  186    assert_eq!(
  187        mem::take(&mut *events.borrow_mut()),
  188        [
  189            ("editor2", "edited"),
  190            ("editor1", "buffer edited"),
  191            ("editor2", "buffer edited"),
  192        ]
  193    );
  194
  195    // No event is emitted when the mutation is a no-op.
  196    _ = editor2.update(cx, |editor, window, cx| {
  197        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  198            s.select_ranges([0..0])
  199        });
  200
  201        editor.backspace(&Backspace, window, cx);
  202    });
  203    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  204}
  205
  206#[gpui::test]
  207fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  208    init_test(cx, |_| {});
  209
  210    let mut now = Instant::now();
  211    let group_interval = Duration::from_millis(1);
  212    let buffer = cx.new(|cx| {
  213        let mut buf = language::Buffer::local("123456", cx);
  214        buf.set_group_interval(group_interval);
  215        buf
  216    });
  217    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  218    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  219
  220    _ = editor.update(cx, |editor, window, cx| {
  221        editor.start_transaction_at(now, window, cx);
  222        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  223            s.select_ranges([2..4])
  224        });
  225
  226        editor.insert("cd", window, cx);
  227        editor.end_transaction_at(now, cx);
  228        assert_eq!(editor.text(cx), "12cd56");
  229        assert_eq!(
  230            editor.selections.ranges(&editor.display_snapshot(cx)),
  231            vec![4..4]
  232        );
  233
  234        editor.start_transaction_at(now, window, cx);
  235        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  236            s.select_ranges([4..5])
  237        });
  238        editor.insert("e", window, cx);
  239        editor.end_transaction_at(now, cx);
  240        assert_eq!(editor.text(cx), "12cde6");
  241        assert_eq!(
  242            editor.selections.ranges(&editor.display_snapshot(cx)),
  243            vec![5..5]
  244        );
  245
  246        now += group_interval + Duration::from_millis(1);
  247        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  248            s.select_ranges([2..2])
  249        });
  250
  251        // Simulate an edit in another editor
  252        buffer.update(cx, |buffer, cx| {
  253            buffer.start_transaction_at(now, cx);
  254            buffer.edit([(0..1, "a")], None, cx);
  255            buffer.edit([(1..1, "b")], None, cx);
  256            buffer.end_transaction_at(now, cx);
  257        });
  258
  259        assert_eq!(editor.text(cx), "ab2cde6");
  260        assert_eq!(
  261            editor.selections.ranges(&editor.display_snapshot(cx)),
  262            vec![3..3]
  263        );
  264
  265        // Last transaction happened past the group interval in a different editor.
  266        // Undo it individually and don't restore selections.
  267        editor.undo(&Undo, window, cx);
  268        assert_eq!(editor.text(cx), "12cde6");
  269        assert_eq!(
  270            editor.selections.ranges(&editor.display_snapshot(cx)),
  271            vec![2..2]
  272        );
  273
  274        // First two transactions happened within the group interval in this editor.
  275        // Undo them together and restore selections.
  276        editor.undo(&Undo, window, cx);
  277        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  278        assert_eq!(editor.text(cx), "123456");
  279        assert_eq!(
  280            editor.selections.ranges(&editor.display_snapshot(cx)),
  281            vec![0..0]
  282        );
  283
  284        // Redo the first two transactions together.
  285        editor.redo(&Redo, window, cx);
  286        assert_eq!(editor.text(cx), "12cde6");
  287        assert_eq!(
  288            editor.selections.ranges(&editor.display_snapshot(cx)),
  289            vec![5..5]
  290        );
  291
  292        // Redo the last transaction on its own.
  293        editor.redo(&Redo, window, cx);
  294        assert_eq!(editor.text(cx), "ab2cde6");
  295        assert_eq!(
  296            editor.selections.ranges(&editor.display_snapshot(cx)),
  297            vec![6..6]
  298        );
  299
  300        // Test empty transactions.
  301        editor.start_transaction_at(now, window, cx);
  302        editor.end_transaction_at(now, cx);
  303        editor.undo(&Undo, window, cx);
  304        assert_eq!(editor.text(cx), "12cde6");
  305    });
  306}
  307
  308#[gpui::test]
  309fn test_ime_composition(cx: &mut TestAppContext) {
  310    init_test(cx, |_| {});
  311
  312    let buffer = cx.new(|cx| {
  313        let mut buffer = language::Buffer::local("abcde", cx);
  314        // Ensure automatic grouping doesn't occur.
  315        buffer.set_group_interval(Duration::ZERO);
  316        buffer
  317    });
  318
  319    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  320    cx.add_window(|window, cx| {
  321        let mut editor = build_editor(buffer.clone(), window, cx);
  322
  323        // Start a new IME composition.
  324        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  325        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
  326        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  327        assert_eq!(editor.text(cx), "äbcde");
  328        assert_eq!(
  329            editor.marked_text_ranges(cx),
  330            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  331        );
  332
  333        // Finalize IME composition.
  334        editor.replace_text_in_range(None, "ā", window, cx);
  335        assert_eq!(editor.text(cx), "ābcde");
  336        assert_eq!(editor.marked_text_ranges(cx), None);
  337
  338        // IME composition edits are grouped and are undone/redone at once.
  339        editor.undo(&Default::default(), window, cx);
  340        assert_eq!(editor.text(cx), "abcde");
  341        assert_eq!(editor.marked_text_ranges(cx), None);
  342        editor.redo(&Default::default(), window, cx);
  343        assert_eq!(editor.text(cx), "ābcde");
  344        assert_eq!(editor.marked_text_ranges(cx), None);
  345
  346        // Start a new IME composition.
  347        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  348        assert_eq!(
  349            editor.marked_text_ranges(cx),
  350            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  351        );
  352
  353        // Undoing during an IME composition cancels it.
  354        editor.undo(&Default::default(), window, cx);
  355        assert_eq!(editor.text(cx), "ābcde");
  356        assert_eq!(editor.marked_text_ranges(cx), None);
  357
  358        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  359        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  360        assert_eq!(editor.text(cx), "ābcdè");
  361        assert_eq!(
  362            editor.marked_text_ranges(cx),
  363            Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
  364        );
  365
  366        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  367        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  368        assert_eq!(editor.text(cx), "ābcdę");
  369        assert_eq!(editor.marked_text_ranges(cx), None);
  370
  371        // Start a new IME composition with multiple cursors.
  372        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  373            s.select_ranges([
  374                OffsetUtf16(1)..OffsetUtf16(1),
  375                OffsetUtf16(3)..OffsetUtf16(3),
  376                OffsetUtf16(5)..OffsetUtf16(5),
  377            ])
  378        });
  379        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  380        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  381        assert_eq!(
  382            editor.marked_text_ranges(cx),
  383            Some(vec![
  384                OffsetUtf16(0)..OffsetUtf16(3),
  385                OffsetUtf16(4)..OffsetUtf16(7),
  386                OffsetUtf16(8)..OffsetUtf16(11)
  387            ])
  388        );
  389
  390        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  391        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  392        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  393        assert_eq!(
  394            editor.marked_text_ranges(cx),
  395            Some(vec![
  396                OffsetUtf16(1)..OffsetUtf16(2),
  397                OffsetUtf16(5)..OffsetUtf16(6),
  398                OffsetUtf16(9)..OffsetUtf16(10)
  399            ])
  400        );
  401
  402        // Finalize IME composition with multiple cursors.
  403        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  404        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  405        assert_eq!(editor.marked_text_ranges(cx), None);
  406
  407        editor
  408    });
  409}
  410
  411#[gpui::test]
  412fn test_selection_with_mouse(cx: &mut TestAppContext) {
  413    init_test(cx, |_| {});
  414
  415    let editor = cx.add_window(|window, cx| {
  416        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  417        build_editor(buffer, window, cx)
  418    });
  419
  420    _ = editor.update(cx, |editor, window, cx| {
  421        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  422    });
  423    assert_eq!(
  424        editor
  425            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  426            .unwrap(),
  427        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  428    );
  429
  430    _ = editor.update(cx, |editor, 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| display_ranges(editor, cx))
  443            .unwrap(),
  444        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  445    );
  446
  447    _ = editor.update(cx, |editor, window, cx| {
  448        editor.update_selection(
  449            DisplayPoint::new(DisplayRow(1), 1),
  450            0,
  451            gpui::Point::<f32>::default(),
  452            window,
  453            cx,
  454        );
  455    });
  456
  457    assert_eq!(
  458        editor
  459            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  460            .unwrap(),
  461        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  462    );
  463
  464    _ = editor.update(cx, |editor, window, cx| {
  465        editor.end_selection(window, cx);
  466        editor.update_selection(
  467            DisplayPoint::new(DisplayRow(3), 3),
  468            0,
  469            gpui::Point::<f32>::default(),
  470            window,
  471            cx,
  472        );
  473    });
  474
  475    assert_eq!(
  476        editor
  477            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  478            .unwrap(),
  479        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  480    );
  481
  482    _ = editor.update(cx, |editor, window, cx| {
  483        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  484        editor.update_selection(
  485            DisplayPoint::new(DisplayRow(0), 0),
  486            0,
  487            gpui::Point::<f32>::default(),
  488            window,
  489            cx,
  490        );
  491    });
  492
  493    assert_eq!(
  494        editor
  495            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  496            .unwrap(),
  497        [
  498            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  499            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  500        ]
  501    );
  502
  503    _ = editor.update(cx, |editor, window, cx| {
  504        editor.end_selection(window, cx);
  505    });
  506
  507    assert_eq!(
  508        editor
  509            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  510            .unwrap(),
  511        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  512    );
  513}
  514
  515#[gpui::test]
  516fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  517    init_test(cx, |_| {});
  518
  519    let editor = cx.add_window(|window, cx| {
  520        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  521        build_editor(buffer, window, cx)
  522    });
  523
  524    _ = editor.update(cx, |editor, window, cx| {
  525        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  526    });
  527
  528    _ = editor.update(cx, |editor, window, cx| {
  529        editor.end_selection(window, cx);
  530    });
  531
  532    _ = editor.update(cx, |editor, window, cx| {
  533        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  534    });
  535
  536    _ = editor.update(cx, |editor, window, cx| {
  537        editor.end_selection(window, cx);
  538    });
  539
  540    assert_eq!(
  541        editor
  542            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  543            .unwrap(),
  544        [
  545            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  546            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  547        ]
  548    );
  549
  550    _ = editor.update(cx, |editor, window, cx| {
  551        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  552    });
  553
  554    _ = editor.update(cx, |editor, window, cx| {
  555        editor.end_selection(window, cx);
  556    });
  557
  558    assert_eq!(
  559        editor
  560            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  561            .unwrap(),
  562        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  563    );
  564}
  565
  566#[gpui::test]
  567fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  568    init_test(cx, |_| {});
  569
  570    let editor = cx.add_window(|window, cx| {
  571        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  572        build_editor(buffer, window, cx)
  573    });
  574
  575    _ = editor.update(cx, |editor, window, cx| {
  576        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  577        assert_eq!(
  578            display_ranges(editor, cx),
  579            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  580        );
  581    });
  582
  583    _ = editor.update(cx, |editor, window, cx| {
  584        editor.update_selection(
  585            DisplayPoint::new(DisplayRow(3), 3),
  586            0,
  587            gpui::Point::<f32>::default(),
  588            window,
  589            cx,
  590        );
  591        assert_eq!(
  592            display_ranges(editor, cx),
  593            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  594        );
  595    });
  596
  597    _ = editor.update(cx, |editor, window, cx| {
  598        editor.cancel(&Cancel, window, cx);
  599        editor.update_selection(
  600            DisplayPoint::new(DisplayRow(1), 1),
  601            0,
  602            gpui::Point::<f32>::default(),
  603            window,
  604            cx,
  605        );
  606        assert_eq!(
  607            display_ranges(editor, cx),
  608            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  609        );
  610    });
  611}
  612
  613#[gpui::test]
  614fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  615    init_test(cx, |_| {});
  616
  617    let editor = cx.add_window(|window, cx| {
  618        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  619        build_editor(buffer, window, cx)
  620    });
  621
  622    _ = editor.update(cx, |editor, window, cx| {
  623        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  624        assert_eq!(
  625            display_ranges(editor, cx),
  626            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  627        );
  628
  629        editor.move_down(&Default::default(), window, cx);
  630        assert_eq!(
  631            display_ranges(editor, cx),
  632            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  633        );
  634
  635        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  636        assert_eq!(
  637            display_ranges(editor, cx),
  638            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  639        );
  640
  641        editor.move_up(&Default::default(), window, cx);
  642        assert_eq!(
  643            display_ranges(editor, cx),
  644            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  645        );
  646    });
  647}
  648
  649#[gpui::test]
  650fn test_extending_selection(cx: &mut TestAppContext) {
  651    init_test(cx, |_| {});
  652
  653    let editor = cx.add_window(|window, cx| {
  654        let buffer = MultiBuffer::build_simple("aaa bbb ccc ddd eee", cx);
  655        build_editor(buffer, window, cx)
  656    });
  657
  658    _ = editor.update(cx, |editor, window, cx| {
  659        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), false, 1, window, cx);
  660        editor.end_selection(window, cx);
  661        assert_eq!(
  662            display_ranges(editor, cx),
  663            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)]
  664        );
  665
  666        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  667        editor.end_selection(window, cx);
  668        assert_eq!(
  669            display_ranges(editor, cx),
  670            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 10)]
  671        );
  672
  673        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  674        editor.end_selection(window, cx);
  675        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 2, window, cx);
  676        assert_eq!(
  677            display_ranges(editor, cx),
  678            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 11)]
  679        );
  680
  681        editor.update_selection(
  682            DisplayPoint::new(DisplayRow(0), 1),
  683            0,
  684            gpui::Point::<f32>::default(),
  685            window,
  686            cx,
  687        );
  688        editor.end_selection(window, cx);
  689        assert_eq!(
  690            display_ranges(editor, cx),
  691            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 0)]
  692        );
  693
  694        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), true, 1, window, cx);
  695        editor.end_selection(window, cx);
  696        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), true, 2, window, cx);
  697        editor.end_selection(window, cx);
  698        assert_eq!(
  699            display_ranges(editor, cx),
  700            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 7)]
  701        );
  702
  703        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  704        assert_eq!(
  705            display_ranges(editor, cx),
  706            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 11)]
  707        );
  708
  709        editor.update_selection(
  710            DisplayPoint::new(DisplayRow(0), 6),
  711            0,
  712            gpui::Point::<f32>::default(),
  713            window,
  714            cx,
  715        );
  716        assert_eq!(
  717            display_ranges(editor, cx),
  718            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 7)]
  719        );
  720
  721        editor.update_selection(
  722            DisplayPoint::new(DisplayRow(0), 1),
  723            0,
  724            gpui::Point::<f32>::default(),
  725            window,
  726            cx,
  727        );
  728        editor.end_selection(window, cx);
  729        assert_eq!(
  730            display_ranges(editor, cx),
  731            [DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 0)]
  732        );
  733    });
  734}
  735
  736#[gpui::test]
  737fn test_clone(cx: &mut TestAppContext) {
  738    init_test(cx, |_| {});
  739
  740    let (text, selection_ranges) = marked_text_ranges(
  741        indoc! {"
  742            one
  743            two
  744            threeˇ
  745            four
  746            fiveˇ
  747        "},
  748        true,
  749    );
  750
  751    let editor = cx.add_window(|window, cx| {
  752        let buffer = MultiBuffer::build_simple(&text, cx);
  753        build_editor(buffer, window, cx)
  754    });
  755
  756    _ = editor.update(cx, |editor, window, cx| {
  757        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  758            s.select_ranges(selection_ranges.clone())
  759        });
  760        editor.fold_creases(
  761            vec![
  762                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  763                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  764            ],
  765            true,
  766            window,
  767            cx,
  768        );
  769    });
  770
  771    let cloned_editor = editor
  772        .update(cx, |editor, _, cx| {
  773            cx.open_window(Default::default(), |window, cx| {
  774                cx.new(|cx| editor.clone(window, cx))
  775            })
  776        })
  777        .unwrap()
  778        .unwrap();
  779
  780    let snapshot = editor
  781        .update(cx, |e, window, cx| e.snapshot(window, cx))
  782        .unwrap();
  783    let cloned_snapshot = cloned_editor
  784        .update(cx, |e, window, cx| e.snapshot(window, cx))
  785        .unwrap();
  786
  787    assert_eq!(
  788        cloned_editor
  789            .update(cx, |e, _, cx| e.display_text(cx))
  790            .unwrap(),
  791        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  792    );
  793    assert_eq!(
  794        cloned_snapshot
  795            .folds_in_range(0..text.len())
  796            .collect::<Vec<_>>(),
  797        snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
  798    );
  799    assert_set_eq!(
  800        cloned_editor
  801            .update(cx, |editor, _, cx| editor
  802                .selections
  803                .ranges::<Point>(&editor.display_snapshot(cx)))
  804            .unwrap(),
  805        editor
  806            .update(cx, |editor, _, cx| editor
  807                .selections
  808                .ranges(&editor.display_snapshot(cx)))
  809            .unwrap()
  810    );
  811    assert_set_eq!(
  812        cloned_editor
  813            .update(cx, |e, _window, cx| e
  814                .selections
  815                .display_ranges(&e.display_snapshot(cx)))
  816            .unwrap(),
  817        editor
  818            .update(cx, |e, _, cx| e
  819                .selections
  820                .display_ranges(&e.display_snapshot(cx)))
  821            .unwrap()
  822    );
  823}
  824
  825#[gpui::test]
  826async fn test_navigation_history(cx: &mut TestAppContext) {
  827    init_test(cx, |_| {});
  828
  829    use workspace::item::Item;
  830
  831    let fs = FakeFs::new(cx.executor());
  832    let project = Project::test(fs, [], cx).await;
  833    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  834    let pane = workspace
  835        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  836        .unwrap();
  837
  838    _ = workspace.update(cx, |_v, window, cx| {
  839        cx.new(|cx| {
  840            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  841            let mut editor = build_editor(buffer, window, cx);
  842            let handle = cx.entity();
  843            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  844
  845            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  846                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  847            }
  848
  849            // Move the cursor a small distance.
  850            // Nothing is added to the navigation history.
  851            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  852                s.select_display_ranges([
  853                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  854                ])
  855            });
  856            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  857                s.select_display_ranges([
  858                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  859                ])
  860            });
  861            assert!(pop_history(&mut editor, cx).is_none());
  862
  863            // Move the cursor a large distance.
  864            // The history can jump back to the previous position.
  865            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  866                s.select_display_ranges([
  867                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  868                ])
  869            });
  870            let nav_entry = pop_history(&mut editor, cx).unwrap();
  871            editor.navigate(nav_entry.data.unwrap(), window, cx);
  872            assert_eq!(nav_entry.item.id(), cx.entity_id());
  873            assert_eq!(
  874                editor
  875                    .selections
  876                    .display_ranges(&editor.display_snapshot(cx)),
  877                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  878            );
  879            assert!(pop_history(&mut editor, cx).is_none());
  880
  881            // Move the cursor a small distance via the mouse.
  882            // Nothing is added to the navigation history.
  883            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  884            editor.end_selection(window, cx);
  885            assert_eq!(
  886                editor
  887                    .selections
  888                    .display_ranges(&editor.display_snapshot(cx)),
  889                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  890            );
  891            assert!(pop_history(&mut editor, cx).is_none());
  892
  893            // Move the cursor a large distance via the mouse.
  894            // The history can jump back to the previous position.
  895            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  896            editor.end_selection(window, cx);
  897            assert_eq!(
  898                editor
  899                    .selections
  900                    .display_ranges(&editor.display_snapshot(cx)),
  901                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  902            );
  903            let nav_entry = pop_history(&mut editor, cx).unwrap();
  904            editor.navigate(nav_entry.data.unwrap(), window, cx);
  905            assert_eq!(nav_entry.item.id(), cx.entity_id());
  906            assert_eq!(
  907                editor
  908                    .selections
  909                    .display_ranges(&editor.display_snapshot(cx)),
  910                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  911            );
  912            assert!(pop_history(&mut editor, cx).is_none());
  913
  914            // Set scroll position to check later
  915            editor.set_scroll_position(gpui::Point::<f64>::new(5.5, 5.5), window, cx);
  916            let original_scroll_position = editor.scroll_manager.anchor();
  917
  918            // Jump to the end of the document and adjust scroll
  919            editor.move_to_end(&MoveToEnd, window, cx);
  920            editor.set_scroll_position(gpui::Point::<f64>::new(-2.5, -0.5), window, cx);
  921            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  922
  923            let nav_entry = pop_history(&mut editor, cx).unwrap();
  924            editor.navigate(nav_entry.data.unwrap(), window, cx);
  925            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  926
  927            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  928            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  929            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  930            let invalid_point = Point::new(9999, 0);
  931            editor.navigate(
  932                Box::new(NavigationData {
  933                    cursor_anchor: invalid_anchor,
  934                    cursor_position: invalid_point,
  935                    scroll_anchor: ScrollAnchor {
  936                        anchor: invalid_anchor,
  937                        offset: Default::default(),
  938                    },
  939                    scroll_top_row: invalid_point.row,
  940                }),
  941                window,
  942                cx,
  943            );
  944            assert_eq!(
  945                editor
  946                    .selections
  947                    .display_ranges(&editor.display_snapshot(cx)),
  948                &[editor.max_point(cx)..editor.max_point(cx)]
  949            );
  950            assert_eq!(
  951                editor.scroll_position(cx),
  952                gpui::Point::new(0., editor.max_point(cx).row().as_f64())
  953            );
  954
  955            editor
  956        })
  957    });
  958}
  959
  960#[gpui::test]
  961fn test_cancel(cx: &mut TestAppContext) {
  962    init_test(cx, |_| {});
  963
  964    let editor = cx.add_window(|window, cx| {
  965        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  966        build_editor(buffer, window, cx)
  967    });
  968
  969    _ = editor.update(cx, |editor, window, cx| {
  970        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  971        editor.update_selection(
  972            DisplayPoint::new(DisplayRow(1), 1),
  973            0,
  974            gpui::Point::<f32>::default(),
  975            window,
  976            cx,
  977        );
  978        editor.end_selection(window, cx);
  979
  980        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
  981        editor.update_selection(
  982            DisplayPoint::new(DisplayRow(0), 3),
  983            0,
  984            gpui::Point::<f32>::default(),
  985            window,
  986            cx,
  987        );
  988        editor.end_selection(window, cx);
  989        assert_eq!(
  990            display_ranges(editor, cx),
  991            [
  992                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
  993                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
  994            ]
  995        );
  996    });
  997
  998    _ = editor.update(cx, |editor, window, cx| {
  999        editor.cancel(&Cancel, window, cx);
 1000        assert_eq!(
 1001            display_ranges(editor, cx),
 1002            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
 1003        );
 1004    });
 1005
 1006    _ = editor.update(cx, |editor, window, cx| {
 1007        editor.cancel(&Cancel, window, cx);
 1008        assert_eq!(
 1009            display_ranges(editor, cx),
 1010            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
 1011        );
 1012    });
 1013}
 1014
 1015#[gpui::test]
 1016fn test_fold_action(cx: &mut TestAppContext) {
 1017    init_test(cx, |_| {});
 1018
 1019    let editor = cx.add_window(|window, cx| {
 1020        let buffer = MultiBuffer::build_simple(
 1021            &"
 1022                impl Foo {
 1023                    // Hello!
 1024
 1025                    fn a() {
 1026                        1
 1027                    }
 1028
 1029                    fn b() {
 1030                        2
 1031                    }
 1032
 1033                    fn c() {
 1034                        3
 1035                    }
 1036                }
 1037            "
 1038            .unindent(),
 1039            cx,
 1040        );
 1041        build_editor(buffer, window, cx)
 1042    });
 1043
 1044    _ = editor.update(cx, |editor, window, cx| {
 1045        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1046            s.select_display_ranges([
 1047                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
 1048            ]);
 1049        });
 1050        editor.fold(&Fold, window, cx);
 1051        assert_eq!(
 1052            editor.display_text(cx),
 1053            "
 1054                impl Foo {
 1055                    // Hello!
 1056
 1057                    fn a() {
 1058                        1
 1059                    }
 1060
 1061                    fn b() {⋯
 1062                    }
 1063
 1064                    fn c() {⋯
 1065                    }
 1066                }
 1067            "
 1068            .unindent(),
 1069        );
 1070
 1071        editor.fold(&Fold, window, cx);
 1072        assert_eq!(
 1073            editor.display_text(cx),
 1074            "
 1075                impl Foo {⋯
 1076                }
 1077            "
 1078            .unindent(),
 1079        );
 1080
 1081        editor.unfold_lines(&UnfoldLines, window, cx);
 1082        assert_eq!(
 1083            editor.display_text(cx),
 1084            "
 1085                impl Foo {
 1086                    // Hello!
 1087
 1088                    fn a() {
 1089                        1
 1090                    }
 1091
 1092                    fn b() {⋯
 1093                    }
 1094
 1095                    fn c() {⋯
 1096                    }
 1097                }
 1098            "
 1099            .unindent(),
 1100        );
 1101
 1102        editor.unfold_lines(&UnfoldLines, window, cx);
 1103        assert_eq!(
 1104            editor.display_text(cx),
 1105            editor.buffer.read(cx).read(cx).text()
 1106        );
 1107    });
 1108}
 1109
 1110#[gpui::test]
 1111fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
 1112    init_test(cx, |_| {});
 1113
 1114    let editor = cx.add_window(|window, cx| {
 1115        let buffer = MultiBuffer::build_simple(
 1116            &"
 1117                class Foo:
 1118                    # Hello!
 1119
 1120                    def a():
 1121                        print(1)
 1122
 1123                    def b():
 1124                        print(2)
 1125
 1126                    def c():
 1127                        print(3)
 1128            "
 1129            .unindent(),
 1130            cx,
 1131        );
 1132        build_editor(buffer, window, cx)
 1133    });
 1134
 1135    _ = editor.update(cx, |editor, window, cx| {
 1136        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1137            s.select_display_ranges([
 1138                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
 1139            ]);
 1140        });
 1141        editor.fold(&Fold, window, cx);
 1142        assert_eq!(
 1143            editor.display_text(cx),
 1144            "
 1145                class Foo:
 1146                    # Hello!
 1147
 1148                    def a():
 1149                        print(1)
 1150
 1151                    def b():⋯
 1152
 1153                    def c():⋯
 1154            "
 1155            .unindent(),
 1156        );
 1157
 1158        editor.fold(&Fold, window, cx);
 1159        assert_eq!(
 1160            editor.display_text(cx),
 1161            "
 1162                class Foo:⋯
 1163            "
 1164            .unindent(),
 1165        );
 1166
 1167        editor.unfold_lines(&UnfoldLines, window, cx);
 1168        assert_eq!(
 1169            editor.display_text(cx),
 1170            "
 1171                class Foo:
 1172                    # Hello!
 1173
 1174                    def a():
 1175                        print(1)
 1176
 1177                    def b():⋯
 1178
 1179                    def c():⋯
 1180            "
 1181            .unindent(),
 1182        );
 1183
 1184        editor.unfold_lines(&UnfoldLines, window, cx);
 1185        assert_eq!(
 1186            editor.display_text(cx),
 1187            editor.buffer.read(cx).read(cx).text()
 1188        );
 1189    });
 1190}
 1191
 1192#[gpui::test]
 1193fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1194    init_test(cx, |_| {});
 1195
 1196    let editor = cx.add_window(|window, cx| {
 1197        let buffer = MultiBuffer::build_simple(
 1198            &"
 1199                class Foo:
 1200                    # Hello!
 1201
 1202                    def a():
 1203                        print(1)
 1204
 1205                    def b():
 1206                        print(2)
 1207
 1208
 1209                    def c():
 1210                        print(3)
 1211
 1212
 1213            "
 1214            .unindent(),
 1215            cx,
 1216        );
 1217        build_editor(buffer, window, cx)
 1218    });
 1219
 1220    _ = editor.update(cx, |editor, window, cx| {
 1221        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1222            s.select_display_ranges([
 1223                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1224            ]);
 1225        });
 1226        editor.fold(&Fold, window, cx);
 1227        assert_eq!(
 1228            editor.display_text(cx),
 1229            "
 1230                class Foo:
 1231                    # Hello!
 1232
 1233                    def a():
 1234                        print(1)
 1235
 1236                    def b():⋯
 1237
 1238
 1239                    def c():⋯
 1240
 1241
 1242            "
 1243            .unindent(),
 1244        );
 1245
 1246        editor.fold(&Fold, window, cx);
 1247        assert_eq!(
 1248            editor.display_text(cx),
 1249            "
 1250                class Foo:⋯
 1251
 1252
 1253            "
 1254            .unindent(),
 1255        );
 1256
 1257        editor.unfold_lines(&UnfoldLines, window, cx);
 1258        assert_eq!(
 1259            editor.display_text(cx),
 1260            "
 1261                class Foo:
 1262                    # Hello!
 1263
 1264                    def a():
 1265                        print(1)
 1266
 1267                    def b():⋯
 1268
 1269
 1270                    def c():⋯
 1271
 1272
 1273            "
 1274            .unindent(),
 1275        );
 1276
 1277        editor.unfold_lines(&UnfoldLines, window, cx);
 1278        assert_eq!(
 1279            editor.display_text(cx),
 1280            editor.buffer.read(cx).read(cx).text()
 1281        );
 1282    });
 1283}
 1284
 1285#[gpui::test]
 1286fn test_fold_at_level(cx: &mut TestAppContext) {
 1287    init_test(cx, |_| {});
 1288
 1289    let editor = cx.add_window(|window, cx| {
 1290        let buffer = MultiBuffer::build_simple(
 1291            &"
 1292                class Foo:
 1293                    # Hello!
 1294
 1295                    def a():
 1296                        print(1)
 1297
 1298                    def b():
 1299                        print(2)
 1300
 1301
 1302                class Bar:
 1303                    # World!
 1304
 1305                    def a():
 1306                        print(1)
 1307
 1308                    def b():
 1309                        print(2)
 1310
 1311
 1312            "
 1313            .unindent(),
 1314            cx,
 1315        );
 1316        build_editor(buffer, window, cx)
 1317    });
 1318
 1319    _ = editor.update(cx, |editor, window, cx| {
 1320        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1321        assert_eq!(
 1322            editor.display_text(cx),
 1323            "
 1324                class Foo:
 1325                    # Hello!
 1326
 1327                    def a():⋯
 1328
 1329                    def b():⋯
 1330
 1331
 1332                class Bar:
 1333                    # World!
 1334
 1335                    def a():⋯
 1336
 1337                    def b():⋯
 1338
 1339
 1340            "
 1341            .unindent(),
 1342        );
 1343
 1344        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1345        assert_eq!(
 1346            editor.display_text(cx),
 1347            "
 1348                class Foo:⋯
 1349
 1350
 1351                class Bar:⋯
 1352
 1353
 1354            "
 1355            .unindent(),
 1356        );
 1357
 1358        editor.unfold_all(&UnfoldAll, window, cx);
 1359        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1360        assert_eq!(
 1361            editor.display_text(cx),
 1362            "
 1363                class Foo:
 1364                    # Hello!
 1365
 1366                    def a():
 1367                        print(1)
 1368
 1369                    def b():
 1370                        print(2)
 1371
 1372
 1373                class Bar:
 1374                    # World!
 1375
 1376                    def a():
 1377                        print(1)
 1378
 1379                    def b():
 1380                        print(2)
 1381
 1382
 1383            "
 1384            .unindent(),
 1385        );
 1386
 1387        assert_eq!(
 1388            editor.display_text(cx),
 1389            editor.buffer.read(cx).read(cx).text()
 1390        );
 1391        let (_, positions) = marked_text_ranges(
 1392            &"
 1393                       class Foo:
 1394                           # Hello!
 1395
 1396                           def a():
 1397                              print(1)
 1398
 1399                           def b():
 1400                               p«riˇ»nt(2)
 1401
 1402
 1403                       class Bar:
 1404                           # World!
 1405
 1406                           def a():
 1407                               «ˇprint(1)
 1408
 1409                           def b():
 1410                               print(2)»
 1411
 1412
 1413                   "
 1414            .unindent(),
 1415            true,
 1416        );
 1417
 1418        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 1419            s.select_ranges(positions)
 1420        });
 1421
 1422        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1423        assert_eq!(
 1424            editor.display_text(cx),
 1425            "
 1426                class Foo:
 1427                    # Hello!
 1428
 1429                    def a():⋯
 1430
 1431                    def b():
 1432                        print(2)
 1433
 1434
 1435                class Bar:
 1436                    # World!
 1437
 1438                    def a():
 1439                        print(1)
 1440
 1441                    def b():
 1442                        print(2)
 1443
 1444
 1445            "
 1446            .unindent(),
 1447        );
 1448    });
 1449}
 1450
 1451#[gpui::test]
 1452fn test_move_cursor(cx: &mut TestAppContext) {
 1453    init_test(cx, |_| {});
 1454
 1455    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1456    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1457
 1458    buffer.update(cx, |buffer, cx| {
 1459        buffer.edit(
 1460            vec![
 1461                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1462                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1463            ],
 1464            None,
 1465            cx,
 1466        );
 1467    });
 1468    _ = editor.update(cx, |editor, window, cx| {
 1469        assert_eq!(
 1470            display_ranges(editor, cx),
 1471            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1472        );
 1473
 1474        editor.move_down(&MoveDown, window, cx);
 1475        assert_eq!(
 1476            display_ranges(editor, cx),
 1477            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1478        );
 1479
 1480        editor.move_right(&MoveRight, window, cx);
 1481        assert_eq!(
 1482            display_ranges(editor, cx),
 1483            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1484        );
 1485
 1486        editor.move_left(&MoveLeft, window, cx);
 1487        assert_eq!(
 1488            display_ranges(editor, cx),
 1489            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1490        );
 1491
 1492        editor.move_up(&MoveUp, window, cx);
 1493        assert_eq!(
 1494            display_ranges(editor, cx),
 1495            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1496        );
 1497
 1498        editor.move_to_end(&MoveToEnd, window, cx);
 1499        assert_eq!(
 1500            display_ranges(editor, cx),
 1501            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1502        );
 1503
 1504        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1505        assert_eq!(
 1506            display_ranges(editor, cx),
 1507            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1508        );
 1509
 1510        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1511            s.select_display_ranges([
 1512                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1513            ]);
 1514        });
 1515        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1516        assert_eq!(
 1517            display_ranges(editor, cx),
 1518            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1519        );
 1520
 1521        editor.select_to_end(&SelectToEnd, window, cx);
 1522        assert_eq!(
 1523            display_ranges(editor, cx),
 1524            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1525        );
 1526    });
 1527}
 1528
 1529#[gpui::test]
 1530fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1531    init_test(cx, |_| {});
 1532
 1533    let editor = cx.add_window(|window, cx| {
 1534        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1535        build_editor(buffer, window, cx)
 1536    });
 1537
 1538    assert_eq!('🟥'.len_utf8(), 4);
 1539    assert_eq!('α'.len_utf8(), 2);
 1540
 1541    _ = editor.update(cx, |editor, window, cx| {
 1542        editor.fold_creases(
 1543            vec![
 1544                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1545                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1546                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1547            ],
 1548            true,
 1549            window,
 1550            cx,
 1551        );
 1552        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1553
 1554        editor.move_right(&MoveRight, window, cx);
 1555        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥".len())]);
 1556        editor.move_right(&MoveRight, window, cx);
 1557        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥🟧".len())]);
 1558        editor.move_right(&MoveRight, window, cx);
 1559        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥🟧⋯".len())]);
 1560
 1561        editor.move_down(&MoveDown, window, cx);
 1562        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯e".len())]);
 1563        editor.move_left(&MoveLeft, window, cx);
 1564        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯".len())]);
 1565        editor.move_left(&MoveLeft, window, cx);
 1566        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab".len())]);
 1567        editor.move_left(&MoveLeft, window, cx);
 1568        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "a".len())]);
 1569
 1570        editor.move_down(&MoveDown, window, cx);
 1571        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "α".len())]);
 1572        editor.move_right(&MoveRight, window, cx);
 1573        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ".len())]);
 1574        editor.move_right(&MoveRight, window, cx);
 1575        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ⋯".len())]);
 1576        editor.move_right(&MoveRight, window, cx);
 1577        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ⋯ε".len())]);
 1578
 1579        editor.move_up(&MoveUp, window, cx);
 1580        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯e".len())]);
 1581        editor.move_down(&MoveDown, window, cx);
 1582        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ⋯ε".len())]);
 1583        editor.move_up(&MoveUp, window, cx);
 1584        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯e".len())]);
 1585
 1586        editor.move_up(&MoveUp, window, cx);
 1587        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥🟧".len())]);
 1588        editor.move_left(&MoveLeft, window, cx);
 1589        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥".len())]);
 1590        editor.move_left(&MoveLeft, window, cx);
 1591        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "".len())]);
 1592    });
 1593}
 1594
 1595#[gpui::test]
 1596fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1597    init_test(cx, |_| {});
 1598
 1599    let editor = cx.add_window(|window, cx| {
 1600        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1601        build_editor(buffer, window, cx)
 1602    });
 1603    _ = editor.update(cx, |editor, window, cx| {
 1604        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1605            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1606        });
 1607
 1608        // moving above start of document should move selection to start of document,
 1609        // but the next move down should still be at the original goal_x
 1610        editor.move_up(&MoveUp, window, cx);
 1611        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "".len())]);
 1612
 1613        editor.move_down(&MoveDown, window, cx);
 1614        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "abcd".len())]);
 1615
 1616        editor.move_down(&MoveDown, window, cx);
 1617        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβγ".len())]);
 1618
 1619        editor.move_down(&MoveDown, window, cx);
 1620        assert_eq!(display_ranges(editor, cx), &[empty_range(3, "abcd".len())]);
 1621
 1622        editor.move_down(&MoveDown, window, cx);
 1623        assert_eq!(display_ranges(editor, cx), &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]);
 1624
 1625        // moving past end of document should not change goal_x
 1626        editor.move_down(&MoveDown, window, cx);
 1627        assert_eq!(display_ranges(editor, cx), &[empty_range(5, "".len())]);
 1628
 1629        editor.move_down(&MoveDown, window, cx);
 1630        assert_eq!(display_ranges(editor, cx), &[empty_range(5, "".len())]);
 1631
 1632        editor.move_up(&MoveUp, window, cx);
 1633        assert_eq!(display_ranges(editor, cx), &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]);
 1634
 1635        editor.move_up(&MoveUp, window, cx);
 1636        assert_eq!(display_ranges(editor, cx), &[empty_range(3, "abcd".len())]);
 1637
 1638        editor.move_up(&MoveUp, window, cx);
 1639        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβγ".len())]);
 1640    });
 1641}
 1642
 1643#[gpui::test]
 1644fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1645    init_test(cx, |_| {});
 1646    let move_to_beg = MoveToBeginningOfLine {
 1647        stop_at_soft_wraps: true,
 1648        stop_at_indent: true,
 1649    };
 1650
 1651    let delete_to_beg = DeleteToBeginningOfLine {
 1652        stop_at_indent: false,
 1653    };
 1654
 1655    let move_to_end = MoveToEndOfLine {
 1656        stop_at_soft_wraps: true,
 1657    };
 1658
 1659    let editor = cx.add_window(|window, cx| {
 1660        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1661        build_editor(buffer, window, cx)
 1662    });
 1663    _ = editor.update(cx, |editor, window, cx| {
 1664        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1665            s.select_display_ranges([
 1666                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1667                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1668            ]);
 1669        });
 1670    });
 1671
 1672    _ = editor.update(cx, |editor, window, cx| {
 1673        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1674        assert_eq!(
 1675            display_ranges(editor, cx),
 1676            &[
 1677                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1678                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1679            ]
 1680        );
 1681    });
 1682
 1683    _ = editor.update(cx, |editor, window, cx| {
 1684        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1685        assert_eq!(
 1686            display_ranges(editor, cx),
 1687            &[
 1688                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1689                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1690            ]
 1691        );
 1692    });
 1693
 1694    _ = editor.update(cx, |editor, window, cx| {
 1695        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1696        assert_eq!(
 1697            display_ranges(editor, cx),
 1698            &[
 1699                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1700                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1701            ]
 1702        );
 1703    });
 1704
 1705    _ = editor.update(cx, |editor, window, cx| {
 1706        editor.move_to_end_of_line(&move_to_end, window, cx);
 1707        assert_eq!(
 1708            display_ranges(editor, cx),
 1709            &[
 1710                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1711                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1712            ]
 1713        );
 1714    });
 1715
 1716    // Moving to the end of line again is a no-op.
 1717    _ = editor.update(cx, |editor, window, cx| {
 1718        editor.move_to_end_of_line(&move_to_end, window, cx);
 1719        assert_eq!(
 1720            display_ranges(editor, cx),
 1721            &[
 1722                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1723                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1724            ]
 1725        );
 1726    });
 1727
 1728    _ = editor.update(cx, |editor, window, cx| {
 1729        editor.move_left(&MoveLeft, window, cx);
 1730        editor.select_to_beginning_of_line(
 1731            &SelectToBeginningOfLine {
 1732                stop_at_soft_wraps: true,
 1733                stop_at_indent: true,
 1734            },
 1735            window,
 1736            cx,
 1737        );
 1738        assert_eq!(
 1739            display_ranges(editor, cx),
 1740            &[
 1741                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1742                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1743            ]
 1744        );
 1745    });
 1746
 1747    _ = editor.update(cx, |editor, window, cx| {
 1748        editor.select_to_beginning_of_line(
 1749            &SelectToBeginningOfLine {
 1750                stop_at_soft_wraps: true,
 1751                stop_at_indent: true,
 1752            },
 1753            window,
 1754            cx,
 1755        );
 1756        assert_eq!(
 1757            display_ranges(editor, cx),
 1758            &[
 1759                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1760                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1761            ]
 1762        );
 1763    });
 1764
 1765    _ = editor.update(cx, |editor, window, cx| {
 1766        editor.select_to_beginning_of_line(
 1767            &SelectToBeginningOfLine {
 1768                stop_at_soft_wraps: true,
 1769                stop_at_indent: true,
 1770            },
 1771            window,
 1772            cx,
 1773        );
 1774        assert_eq!(
 1775            display_ranges(editor, cx),
 1776            &[
 1777                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1778                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1779            ]
 1780        );
 1781    });
 1782
 1783    _ = editor.update(cx, |editor, window, cx| {
 1784        editor.select_to_end_of_line(
 1785            &SelectToEndOfLine {
 1786                stop_at_soft_wraps: true,
 1787            },
 1788            window,
 1789            cx,
 1790        );
 1791        assert_eq!(
 1792            display_ranges(editor, cx),
 1793            &[
 1794                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1795                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1796            ]
 1797        );
 1798    });
 1799
 1800    _ = editor.update(cx, |editor, window, cx| {
 1801        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1802        assert_eq!(editor.display_text(cx), "ab\n  de");
 1803        assert_eq!(
 1804            display_ranges(editor, cx),
 1805            &[
 1806                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1807                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1808            ]
 1809        );
 1810    });
 1811
 1812    _ = editor.update(cx, |editor, window, cx| {
 1813        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1814        assert_eq!(editor.display_text(cx), "\n");
 1815        assert_eq!(
 1816            display_ranges(editor, cx),
 1817            &[
 1818                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1819                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1820            ]
 1821        );
 1822    });
 1823}
 1824
 1825#[gpui::test]
 1826fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1827    init_test(cx, |_| {});
 1828    let move_to_beg = MoveToBeginningOfLine {
 1829        stop_at_soft_wraps: false,
 1830        stop_at_indent: false,
 1831    };
 1832
 1833    let move_to_end = MoveToEndOfLine {
 1834        stop_at_soft_wraps: false,
 1835    };
 1836
 1837    let editor = cx.add_window(|window, cx| {
 1838        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1839        build_editor(buffer, window, cx)
 1840    });
 1841
 1842    _ = editor.update(cx, |editor, window, cx| {
 1843        editor.set_wrap_width(Some(140.0.into()), cx);
 1844
 1845        // We expect the following lines after wrapping
 1846        // ```
 1847        // thequickbrownfox
 1848        // jumpedoverthelazydo
 1849        // gs
 1850        // ```
 1851        // The final `gs` was soft-wrapped onto a new line.
 1852        assert_eq!(
 1853            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1854            editor.display_text(cx),
 1855        );
 1856
 1857        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1858        // Start the cursor at the `k` on the first line
 1859        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1860            s.select_display_ranges([
 1861                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1862            ]);
 1863        });
 1864
 1865        // Moving to the beginning of the line should put us at the beginning of the line.
 1866        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1867        assert_eq!(
 1868            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1869            display_ranges(editor, cx)
 1870        );
 1871
 1872        // Moving to the end of the line should put us at the end of the line.
 1873        editor.move_to_end_of_line(&move_to_end, window, cx);
 1874        assert_eq!(
 1875            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1876            display_ranges(editor, cx)
 1877        );
 1878
 1879        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1880        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1881        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1882            s.select_display_ranges([
 1883                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1884            ]);
 1885        });
 1886
 1887        // Moving to the beginning of the line should put us at the start of the second line of
 1888        // display text, i.e., the `j`.
 1889        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1890        assert_eq!(
 1891            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1892            display_ranges(editor, cx)
 1893        );
 1894
 1895        // Moving to the beginning of the line again should be a no-op.
 1896        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1897        assert_eq!(
 1898            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1899            display_ranges(editor, cx)
 1900        );
 1901
 1902        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1903        // next display line.
 1904        editor.move_to_end_of_line(&move_to_end, window, cx);
 1905        assert_eq!(
 1906            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1907            display_ranges(editor, cx)
 1908        );
 1909
 1910        // Moving to the end of the line again should be a no-op.
 1911        editor.move_to_end_of_line(&move_to_end, window, cx);
 1912        assert_eq!(
 1913            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1914            display_ranges(editor, cx)
 1915        );
 1916    });
 1917}
 1918
 1919#[gpui::test]
 1920fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1921    init_test(cx, |_| {});
 1922
 1923    let move_to_beg = MoveToBeginningOfLine {
 1924        stop_at_soft_wraps: true,
 1925        stop_at_indent: true,
 1926    };
 1927
 1928    let select_to_beg = SelectToBeginningOfLine {
 1929        stop_at_soft_wraps: true,
 1930        stop_at_indent: true,
 1931    };
 1932
 1933    let delete_to_beg = DeleteToBeginningOfLine {
 1934        stop_at_indent: true,
 1935    };
 1936
 1937    let move_to_end = MoveToEndOfLine {
 1938        stop_at_soft_wraps: false,
 1939    };
 1940
 1941    let editor = cx.add_window(|window, cx| {
 1942        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1943        build_editor(buffer, window, cx)
 1944    });
 1945
 1946    _ = editor.update(cx, |editor, window, cx| {
 1947        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1948            s.select_display_ranges([
 1949                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1950                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1951            ]);
 1952        });
 1953
 1954        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 1955        // and the second cursor at the first non-whitespace character in the line.
 1956        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1957        assert_eq!(
 1958            display_ranges(editor, cx),
 1959            &[
 1960                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1961                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1962            ]
 1963        );
 1964
 1965        // Moving to the beginning of the line again should be a no-op for the first cursor,
 1966        // and should move the second cursor to the beginning of the line.
 1967        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1968        assert_eq!(
 1969            display_ranges(editor, cx),
 1970            &[
 1971                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1972                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1973            ]
 1974        );
 1975
 1976        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 1977        // and should move the second cursor back to the first non-whitespace character in the line.
 1978        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1979        assert_eq!(
 1980            display_ranges(editor, cx),
 1981            &[
 1982                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1983                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1984            ]
 1985        );
 1986
 1987        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 1988        // and to the first non-whitespace character in the line for the second cursor.
 1989        editor.move_to_end_of_line(&move_to_end, window, cx);
 1990        editor.move_left(&MoveLeft, window, cx);
 1991        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1992        assert_eq!(
 1993            display_ranges(editor, cx),
 1994            &[
 1995                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1996                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1997            ]
 1998        );
 1999
 2000        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 2001        // and should select to the beginning of the line for the second cursor.
 2002        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 2003        assert_eq!(
 2004            display_ranges(editor, cx),
 2005            &[
 2006                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 2007                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 2008            ]
 2009        );
 2010
 2011        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 2012        // and should delete to the first non-whitespace character in the line for the second cursor.
 2013        editor.move_to_end_of_line(&move_to_end, window, cx);
 2014        editor.move_left(&MoveLeft, window, cx);
 2015        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 2016        assert_eq!(editor.text(cx), "c\n  f");
 2017    });
 2018}
 2019
 2020#[gpui::test]
 2021fn test_beginning_of_line_with_cursor_between_line_start_and_indent(cx: &mut TestAppContext) {
 2022    init_test(cx, |_| {});
 2023
 2024    let move_to_beg = MoveToBeginningOfLine {
 2025        stop_at_soft_wraps: true,
 2026        stop_at_indent: true,
 2027    };
 2028
 2029    let editor = cx.add_window(|window, cx| {
 2030        let buffer = MultiBuffer::build_simple("    hello\nworld", cx);
 2031        build_editor(buffer, window, cx)
 2032    });
 2033
 2034    _ = editor.update(cx, |editor, window, cx| {
 2035        // test cursor between line_start and indent_start
 2036        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2037            s.select_display_ranges([
 2038                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3)
 2039            ]);
 2040        });
 2041
 2042        // cursor should move to line_start
 2043        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2044        assert_eq!(
 2045            display_ranges(editor, cx),
 2046            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 2047        );
 2048
 2049        // cursor should move to indent_start
 2050        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2051        assert_eq!(
 2052            display_ranges(editor, cx),
 2053            &[DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 4)]
 2054        );
 2055
 2056        // cursor should move to back to line_start
 2057        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2058        assert_eq!(
 2059            display_ranges(editor, cx),
 2060            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 2061        );
 2062    });
 2063}
 2064
 2065#[gpui::test]
 2066fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 2067    init_test(cx, |_| {});
 2068
 2069    let editor = cx.add_window(|window, cx| {
 2070        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 2071        build_editor(buffer, window, cx)
 2072    });
 2073    _ = editor.update(cx, |editor, window, cx| {
 2074        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2075            s.select_display_ranges([
 2076                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 2077                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 2078            ])
 2079        });
 2080        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2081        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 2082
 2083        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2084        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 2085
 2086        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2087        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 2088
 2089        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2090        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 2091
 2092        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2093        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 2094
 2095        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2096        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 2097
 2098        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2099        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 2100
 2101        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2102        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 2103
 2104        editor.move_right(&MoveRight, window, cx);
 2105        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2106        assert_selection_ranges(
 2107            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 2108            editor,
 2109            cx,
 2110        );
 2111
 2112        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2113        assert_selection_ranges(
 2114            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 2115            editor,
 2116            cx,
 2117        );
 2118
 2119        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 2120        assert_selection_ranges(
 2121            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 2122            editor,
 2123            cx,
 2124        );
 2125    });
 2126}
 2127
 2128#[gpui::test]
 2129fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 2130    init_test(cx, |_| {});
 2131
 2132    let editor = cx.add_window(|window, cx| {
 2133        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 2134        build_editor(buffer, window, cx)
 2135    });
 2136
 2137    _ = editor.update(cx, |editor, window, cx| {
 2138        editor.set_wrap_width(Some(140.0.into()), cx);
 2139        assert_eq!(
 2140            editor.display_text(cx),
 2141            "use one::{\n    two::three::\n    four::five\n};"
 2142        );
 2143
 2144        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2145            s.select_display_ranges([
 2146                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 2147            ]);
 2148        });
 2149
 2150        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2151        assert_eq!(
 2152            display_ranges(editor, cx),
 2153            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 2154        );
 2155
 2156        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2157        assert_eq!(
 2158            display_ranges(editor, cx),
 2159            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2160        );
 2161
 2162        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2163        assert_eq!(
 2164            display_ranges(editor, cx),
 2165            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2166        );
 2167
 2168        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2169        assert_eq!(
 2170            display_ranges(editor, cx),
 2171            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2172        );
 2173
 2174        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2175        assert_eq!(
 2176            display_ranges(editor, cx),
 2177            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2178        );
 2179
 2180        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2181        assert_eq!(
 2182            display_ranges(editor, cx),
 2183            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2184        );
 2185    });
 2186}
 2187
 2188#[gpui::test]
 2189async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2190    init_test(cx, |_| {});
 2191    let mut cx = EditorTestContext::new(cx).await;
 2192
 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    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2201
 2202    cx.set_state(
 2203        &r#"ˇone
 2204        two
 2205
 2206        three
 2207        fourˇ
 2208        five
 2209
 2210        six"#
 2211            .unindent(),
 2212    );
 2213
 2214    cx.update_editor(|editor, window, cx| {
 2215        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2216    });
 2217    cx.assert_editor_state(
 2218        &r#"one
 2219        two
 2220        ˇ
 2221        three
 2222        four
 2223        five
 2224        ˇ
 2225        six"#
 2226            .unindent(),
 2227    );
 2228
 2229    cx.update_editor(|editor, window, cx| {
 2230        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2231    });
 2232    cx.assert_editor_state(
 2233        &r#"one
 2234        two
 2235
 2236        three
 2237        four
 2238        five
 2239        ˇ
 2240        sixˇ"#
 2241            .unindent(),
 2242    );
 2243
 2244    cx.update_editor(|editor, window, cx| {
 2245        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2246    });
 2247    cx.assert_editor_state(
 2248        &r#"one
 2249        two
 2250
 2251        three
 2252        four
 2253        five
 2254
 2255        sixˇ"#
 2256            .unindent(),
 2257    );
 2258
 2259    cx.update_editor(|editor, window, cx| {
 2260        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2261    });
 2262    cx.assert_editor_state(
 2263        &r#"one
 2264        two
 2265
 2266        three
 2267        four
 2268        five
 2269        ˇ
 2270        six"#
 2271            .unindent(),
 2272    );
 2273
 2274    cx.update_editor(|editor, window, cx| {
 2275        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2276    });
 2277    cx.assert_editor_state(
 2278        &r#"one
 2279        two
 2280        ˇ
 2281        three
 2282        four
 2283        five
 2284
 2285        six"#
 2286            .unindent(),
 2287    );
 2288
 2289    cx.update_editor(|editor, window, cx| {
 2290        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2291    });
 2292    cx.assert_editor_state(
 2293        &r#"ˇone
 2294        two
 2295
 2296        three
 2297        four
 2298        five
 2299
 2300        six"#
 2301            .unindent(),
 2302    );
 2303}
 2304
 2305#[gpui::test]
 2306async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2307    init_test(cx, |_| {});
 2308    let mut cx = EditorTestContext::new(cx).await;
 2309    let line_height = cx.editor(|editor, window, _| {
 2310        editor
 2311            .style()
 2312            .unwrap()
 2313            .text
 2314            .line_height_in_pixels(window.rem_size())
 2315    });
 2316    let window = cx.window;
 2317    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2318
 2319    cx.set_state(
 2320        r#"ˇone
 2321        two
 2322        three
 2323        four
 2324        five
 2325        six
 2326        seven
 2327        eight
 2328        nine
 2329        ten
 2330        "#,
 2331    );
 2332
 2333    cx.update_editor(|editor, window, cx| {
 2334        assert_eq!(
 2335            editor.snapshot(window, cx).scroll_position(),
 2336            gpui::Point::new(0., 0.)
 2337        );
 2338        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2339        assert_eq!(
 2340            editor.snapshot(window, cx).scroll_position(),
 2341            gpui::Point::new(0., 3.)
 2342        );
 2343        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2344        assert_eq!(
 2345            editor.snapshot(window, cx).scroll_position(),
 2346            gpui::Point::new(0., 6.)
 2347        );
 2348        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2349        assert_eq!(
 2350            editor.snapshot(window, cx).scroll_position(),
 2351            gpui::Point::new(0., 3.)
 2352        );
 2353
 2354        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2355        assert_eq!(
 2356            editor.snapshot(window, cx).scroll_position(),
 2357            gpui::Point::new(0., 1.)
 2358        );
 2359        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2360        assert_eq!(
 2361            editor.snapshot(window, cx).scroll_position(),
 2362            gpui::Point::new(0., 3.)
 2363        );
 2364    });
 2365}
 2366
 2367#[gpui::test]
 2368async fn test_autoscroll(cx: &mut TestAppContext) {
 2369    init_test(cx, |_| {});
 2370    let mut cx = EditorTestContext::new(cx).await;
 2371
 2372    let line_height = cx.update_editor(|editor, window, cx| {
 2373        editor.set_vertical_scroll_margin(2, cx);
 2374        editor
 2375            .style()
 2376            .unwrap()
 2377            .text
 2378            .line_height_in_pixels(window.rem_size())
 2379    });
 2380    let window = cx.window;
 2381    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2382
 2383    cx.set_state(
 2384        r#"ˇone
 2385            two
 2386            three
 2387            four
 2388            five
 2389            six
 2390            seven
 2391            eight
 2392            nine
 2393            ten
 2394        "#,
 2395    );
 2396    cx.update_editor(|editor, window, cx| {
 2397        assert_eq!(
 2398            editor.snapshot(window, cx).scroll_position(),
 2399            gpui::Point::new(0., 0.0)
 2400        );
 2401    });
 2402
 2403    // Add a cursor below the visible area. Since both cursors cannot fit
 2404    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2405    // allows the vertical scroll margin below that cursor.
 2406    cx.update_editor(|editor, window, cx| {
 2407        editor.change_selections(Default::default(), window, cx, |selections| {
 2408            selections.select_ranges([
 2409                Point::new(0, 0)..Point::new(0, 0),
 2410                Point::new(6, 0)..Point::new(6, 0),
 2411            ]);
 2412        })
 2413    });
 2414    cx.update_editor(|editor, window, cx| {
 2415        assert_eq!(
 2416            editor.snapshot(window, cx).scroll_position(),
 2417            gpui::Point::new(0., 3.0)
 2418        );
 2419    });
 2420
 2421    // Move down. The editor cursor scrolls down to track the newest cursor.
 2422    cx.update_editor(|editor, window, cx| {
 2423        editor.move_down(&Default::default(), window, cx);
 2424    });
 2425    cx.update_editor(|editor, window, cx| {
 2426        assert_eq!(
 2427            editor.snapshot(window, cx).scroll_position(),
 2428            gpui::Point::new(0., 4.0)
 2429        );
 2430    });
 2431
 2432    // Add a cursor above the visible area. Since both cursors fit on screen,
 2433    // the editor scrolls to show both.
 2434    cx.update_editor(|editor, window, cx| {
 2435        editor.change_selections(Default::default(), window, cx, |selections| {
 2436            selections.select_ranges([
 2437                Point::new(1, 0)..Point::new(1, 0),
 2438                Point::new(6, 0)..Point::new(6, 0),
 2439            ]);
 2440        })
 2441    });
 2442    cx.update_editor(|editor, window, cx| {
 2443        assert_eq!(
 2444            editor.snapshot(window, cx).scroll_position(),
 2445            gpui::Point::new(0., 1.0)
 2446        );
 2447    });
 2448}
 2449
 2450#[gpui::test]
 2451async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2452    init_test(cx, |_| {});
 2453    let mut cx = EditorTestContext::new(cx).await;
 2454
 2455    let line_height = cx.editor(|editor, window, _cx| {
 2456        editor
 2457            .style()
 2458            .unwrap()
 2459            .text
 2460            .line_height_in_pixels(window.rem_size())
 2461    });
 2462    let window = cx.window;
 2463    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2464    cx.set_state(
 2465        &r#"
 2466        ˇone
 2467        two
 2468        threeˇ
 2469        four
 2470        five
 2471        six
 2472        seven
 2473        eight
 2474        nine
 2475        ten
 2476        "#
 2477        .unindent(),
 2478    );
 2479
 2480    cx.update_editor(|editor, window, cx| {
 2481        editor.move_page_down(&MovePageDown::default(), window, cx)
 2482    });
 2483    cx.assert_editor_state(
 2484        &r#"
 2485        one
 2486        two
 2487        three
 2488        ˇfour
 2489        five
 2490        sixˇ
 2491        seven
 2492        eight
 2493        nine
 2494        ten
 2495        "#
 2496        .unindent(),
 2497    );
 2498
 2499    cx.update_editor(|editor, window, cx| {
 2500        editor.move_page_down(&MovePageDown::default(), window, cx)
 2501    });
 2502    cx.assert_editor_state(
 2503        &r#"
 2504        one
 2505        two
 2506        three
 2507        four
 2508        five
 2509        six
 2510        ˇseven
 2511        eight
 2512        nineˇ
 2513        ten
 2514        "#
 2515        .unindent(),
 2516    );
 2517
 2518    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2519    cx.assert_editor_state(
 2520        &r#"
 2521        one
 2522        two
 2523        three
 2524        ˇfour
 2525        five
 2526        sixˇ
 2527        seven
 2528        eight
 2529        nine
 2530        ten
 2531        "#
 2532        .unindent(),
 2533    );
 2534
 2535    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2536    cx.assert_editor_state(
 2537        &r#"
 2538        ˇone
 2539        two
 2540        threeˇ
 2541        four
 2542        five
 2543        six
 2544        seven
 2545        eight
 2546        nine
 2547        ten
 2548        "#
 2549        .unindent(),
 2550    );
 2551
 2552    // Test select collapsing
 2553    cx.update_editor(|editor, window, cx| {
 2554        editor.move_page_down(&MovePageDown::default(), window, cx);
 2555        editor.move_page_down(&MovePageDown::default(), window, cx);
 2556        editor.move_page_down(&MovePageDown::default(), window, cx);
 2557    });
 2558    cx.assert_editor_state(
 2559        &r#"
 2560        one
 2561        two
 2562        three
 2563        four
 2564        five
 2565        six
 2566        seven
 2567        eight
 2568        nine
 2569        ˇten
 2570        ˇ"#
 2571        .unindent(),
 2572    );
 2573}
 2574
 2575#[gpui::test]
 2576async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2577    init_test(cx, |_| {});
 2578    let mut cx = EditorTestContext::new(cx).await;
 2579    cx.set_state("one «two threeˇ» four");
 2580    cx.update_editor(|editor, window, cx| {
 2581        editor.delete_to_beginning_of_line(
 2582            &DeleteToBeginningOfLine {
 2583                stop_at_indent: false,
 2584            },
 2585            window,
 2586            cx,
 2587        );
 2588        assert_eq!(editor.text(cx), " four");
 2589    });
 2590}
 2591
 2592#[gpui::test]
 2593async fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2594    init_test(cx, |_| {});
 2595
 2596    let mut cx = EditorTestContext::new(cx).await;
 2597
 2598    // For an empty selection, the preceding word fragment is deleted.
 2599    // For non-empty selections, only selected characters are deleted.
 2600    cx.set_state("onˇe two t«hreˇ»e four");
 2601    cx.update_editor(|editor, window, cx| {
 2602        editor.delete_to_previous_word_start(
 2603            &DeleteToPreviousWordStart {
 2604                ignore_newlines: false,
 2605                ignore_brackets: false,
 2606            },
 2607            window,
 2608            cx,
 2609        );
 2610    });
 2611    cx.assert_editor_state("ˇe two tˇe four");
 2612
 2613    cx.set_state("e tˇwo te «fˇ»our");
 2614    cx.update_editor(|editor, window, cx| {
 2615        editor.delete_to_next_word_end(
 2616            &DeleteToNextWordEnd {
 2617                ignore_newlines: false,
 2618                ignore_brackets: false,
 2619            },
 2620            window,
 2621            cx,
 2622        );
 2623    });
 2624    cx.assert_editor_state("e tˇ te ˇour");
 2625}
 2626
 2627#[gpui::test]
 2628async fn test_delete_whitespaces(cx: &mut TestAppContext) {
 2629    init_test(cx, |_| {});
 2630
 2631    let mut cx = EditorTestContext::new(cx).await;
 2632
 2633    cx.set_state("here is some text    ˇwith a space");
 2634    cx.update_editor(|editor, window, cx| {
 2635        editor.delete_to_previous_word_start(
 2636            &DeleteToPreviousWordStart {
 2637                ignore_newlines: false,
 2638                ignore_brackets: true,
 2639            },
 2640            window,
 2641            cx,
 2642        );
 2643    });
 2644    // Continuous whitespace sequences are removed entirely, words behind them are not affected by the deletion action.
 2645    cx.assert_editor_state("here is some textˇwith a space");
 2646
 2647    cx.set_state("here is some text    ˇwith a space");
 2648    cx.update_editor(|editor, window, cx| {
 2649        editor.delete_to_previous_word_start(
 2650            &DeleteToPreviousWordStart {
 2651                ignore_newlines: false,
 2652                ignore_brackets: false,
 2653            },
 2654            window,
 2655            cx,
 2656        );
 2657    });
 2658    cx.assert_editor_state("here is some textˇwith a space");
 2659
 2660    cx.set_state("here is some textˇ    with a space");
 2661    cx.update_editor(|editor, window, cx| {
 2662        editor.delete_to_next_word_end(
 2663            &DeleteToNextWordEnd {
 2664                ignore_newlines: false,
 2665                ignore_brackets: true,
 2666            },
 2667            window,
 2668            cx,
 2669        );
 2670    });
 2671    // Same happens in the other direction.
 2672    cx.assert_editor_state("here is some textˇwith a space");
 2673
 2674    cx.set_state("here is some textˇ    with a space");
 2675    cx.update_editor(|editor, window, cx| {
 2676        editor.delete_to_next_word_end(
 2677            &DeleteToNextWordEnd {
 2678                ignore_newlines: false,
 2679                ignore_brackets: false,
 2680            },
 2681            window,
 2682            cx,
 2683        );
 2684    });
 2685    cx.assert_editor_state("here is some textˇwith a space");
 2686
 2687    cx.set_state("here is some textˇ    with a space");
 2688    cx.update_editor(|editor, window, cx| {
 2689        editor.delete_to_next_word_end(
 2690            &DeleteToNextWordEnd {
 2691                ignore_newlines: true,
 2692                ignore_brackets: false,
 2693            },
 2694            window,
 2695            cx,
 2696        );
 2697    });
 2698    cx.assert_editor_state("here is some textˇwith a space");
 2699    cx.update_editor(|editor, window, cx| {
 2700        editor.delete_to_previous_word_start(
 2701            &DeleteToPreviousWordStart {
 2702                ignore_newlines: true,
 2703                ignore_brackets: false,
 2704            },
 2705            window,
 2706            cx,
 2707        );
 2708    });
 2709    cx.assert_editor_state("here is some ˇwith a space");
 2710    cx.update_editor(|editor, window, cx| {
 2711        editor.delete_to_previous_word_start(
 2712            &DeleteToPreviousWordStart {
 2713                ignore_newlines: true,
 2714                ignore_brackets: false,
 2715            },
 2716            window,
 2717            cx,
 2718        );
 2719    });
 2720    // Single whitespaces are removed with the word behind them.
 2721    cx.assert_editor_state("here is ˇwith a space");
 2722    cx.update_editor(|editor, window, cx| {
 2723        editor.delete_to_previous_word_start(
 2724            &DeleteToPreviousWordStart {
 2725                ignore_newlines: true,
 2726                ignore_brackets: false,
 2727            },
 2728            window,
 2729            cx,
 2730        );
 2731    });
 2732    cx.assert_editor_state("here ˇwith a space");
 2733    cx.update_editor(|editor, window, cx| {
 2734        editor.delete_to_previous_word_start(
 2735            &DeleteToPreviousWordStart {
 2736                ignore_newlines: true,
 2737                ignore_brackets: false,
 2738            },
 2739            window,
 2740            cx,
 2741        );
 2742    });
 2743    cx.assert_editor_state("ˇwith a space");
 2744    cx.update_editor(|editor, window, cx| {
 2745        editor.delete_to_previous_word_start(
 2746            &DeleteToPreviousWordStart {
 2747                ignore_newlines: true,
 2748                ignore_brackets: false,
 2749            },
 2750            window,
 2751            cx,
 2752        );
 2753    });
 2754    cx.assert_editor_state("ˇwith a space");
 2755    cx.update_editor(|editor, window, cx| {
 2756        editor.delete_to_next_word_end(
 2757            &DeleteToNextWordEnd {
 2758                ignore_newlines: true,
 2759                ignore_brackets: false,
 2760            },
 2761            window,
 2762            cx,
 2763        );
 2764    });
 2765    // Same happens in the other direction.
 2766    cx.assert_editor_state("ˇ a space");
 2767    cx.update_editor(|editor, window, cx| {
 2768        editor.delete_to_next_word_end(
 2769            &DeleteToNextWordEnd {
 2770                ignore_newlines: true,
 2771                ignore_brackets: false,
 2772            },
 2773            window,
 2774            cx,
 2775        );
 2776    });
 2777    cx.assert_editor_state("ˇ space");
 2778    cx.update_editor(|editor, window, cx| {
 2779        editor.delete_to_next_word_end(
 2780            &DeleteToNextWordEnd {
 2781                ignore_newlines: true,
 2782                ignore_brackets: false,
 2783            },
 2784            window,
 2785            cx,
 2786        );
 2787    });
 2788    cx.assert_editor_state("ˇ");
 2789    cx.update_editor(|editor, window, cx| {
 2790        editor.delete_to_next_word_end(
 2791            &DeleteToNextWordEnd {
 2792                ignore_newlines: true,
 2793                ignore_brackets: false,
 2794            },
 2795            window,
 2796            cx,
 2797        );
 2798    });
 2799    cx.assert_editor_state("ˇ");
 2800    cx.update_editor(|editor, window, cx| {
 2801        editor.delete_to_previous_word_start(
 2802            &DeleteToPreviousWordStart {
 2803                ignore_newlines: true,
 2804                ignore_brackets: false,
 2805            },
 2806            window,
 2807            cx,
 2808        );
 2809    });
 2810    cx.assert_editor_state("ˇ");
 2811}
 2812
 2813#[gpui::test]
 2814async fn test_delete_to_bracket(cx: &mut TestAppContext) {
 2815    init_test(cx, |_| {});
 2816
 2817    let language = Arc::new(
 2818        Language::new(
 2819            LanguageConfig {
 2820                brackets: BracketPairConfig {
 2821                    pairs: vec![
 2822                        BracketPair {
 2823                            start: "\"".to_string(),
 2824                            end: "\"".to_string(),
 2825                            close: true,
 2826                            surround: true,
 2827                            newline: false,
 2828                        },
 2829                        BracketPair {
 2830                            start: "(".to_string(),
 2831                            end: ")".to_string(),
 2832                            close: true,
 2833                            surround: true,
 2834                            newline: true,
 2835                        },
 2836                    ],
 2837                    ..BracketPairConfig::default()
 2838                },
 2839                ..LanguageConfig::default()
 2840            },
 2841            Some(tree_sitter_rust::LANGUAGE.into()),
 2842        )
 2843        .with_brackets_query(
 2844            r#"
 2845                ("(" @open ")" @close)
 2846                ("\"" @open "\"" @close)
 2847            "#,
 2848        )
 2849        .unwrap(),
 2850    );
 2851
 2852    let mut cx = EditorTestContext::new(cx).await;
 2853    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2854
 2855    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2856    cx.update_editor(|editor, window, cx| {
 2857        editor.delete_to_previous_word_start(
 2858            &DeleteToPreviousWordStart {
 2859                ignore_newlines: true,
 2860                ignore_brackets: false,
 2861            },
 2862            window,
 2863            cx,
 2864        );
 2865    });
 2866    // Deletion stops before brackets if asked to not ignore them.
 2867    cx.assert_editor_state(r#"macro!("ˇCOMMENT");"#);
 2868    cx.update_editor(|editor, window, cx| {
 2869        editor.delete_to_previous_word_start(
 2870            &DeleteToPreviousWordStart {
 2871                ignore_newlines: true,
 2872                ignore_brackets: false,
 2873            },
 2874            window,
 2875            cx,
 2876        );
 2877    });
 2878    // Deletion has to remove a single bracket and then stop again.
 2879    cx.assert_editor_state(r#"macro!(ˇCOMMENT");"#);
 2880
 2881    cx.update_editor(|editor, window, cx| {
 2882        editor.delete_to_previous_word_start(
 2883            &DeleteToPreviousWordStart {
 2884                ignore_newlines: true,
 2885                ignore_brackets: false,
 2886            },
 2887            window,
 2888            cx,
 2889        );
 2890    });
 2891    cx.assert_editor_state(r#"macro!ˇCOMMENT");"#);
 2892
 2893    cx.update_editor(|editor, window, cx| {
 2894        editor.delete_to_previous_word_start(
 2895            &DeleteToPreviousWordStart {
 2896                ignore_newlines: true,
 2897                ignore_brackets: false,
 2898            },
 2899            window,
 2900            cx,
 2901        );
 2902    });
 2903    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2904
 2905    cx.update_editor(|editor, window, cx| {
 2906        editor.delete_to_previous_word_start(
 2907            &DeleteToPreviousWordStart {
 2908                ignore_newlines: true,
 2909                ignore_brackets: false,
 2910            },
 2911            window,
 2912            cx,
 2913        );
 2914    });
 2915    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2916
 2917    cx.update_editor(|editor, window, cx| {
 2918        editor.delete_to_next_word_end(
 2919            &DeleteToNextWordEnd {
 2920                ignore_newlines: true,
 2921                ignore_brackets: false,
 2922            },
 2923            window,
 2924            cx,
 2925        );
 2926    });
 2927    // Brackets on the right are not paired anymore, hence deletion does not stop at them
 2928    cx.assert_editor_state(r#"ˇ");"#);
 2929
 2930    cx.update_editor(|editor, window, cx| {
 2931        editor.delete_to_next_word_end(
 2932            &DeleteToNextWordEnd {
 2933                ignore_newlines: true,
 2934                ignore_brackets: false,
 2935            },
 2936            window,
 2937            cx,
 2938        );
 2939    });
 2940    cx.assert_editor_state(r#"ˇ"#);
 2941
 2942    cx.update_editor(|editor, window, cx| {
 2943        editor.delete_to_next_word_end(
 2944            &DeleteToNextWordEnd {
 2945                ignore_newlines: true,
 2946                ignore_brackets: false,
 2947            },
 2948            window,
 2949            cx,
 2950        );
 2951    });
 2952    cx.assert_editor_state(r#"ˇ"#);
 2953
 2954    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2955    cx.update_editor(|editor, window, cx| {
 2956        editor.delete_to_previous_word_start(
 2957            &DeleteToPreviousWordStart {
 2958                ignore_newlines: true,
 2959                ignore_brackets: true,
 2960            },
 2961            window,
 2962            cx,
 2963        );
 2964    });
 2965    cx.assert_editor_state(r#"macroˇCOMMENT");"#);
 2966}
 2967
 2968#[gpui::test]
 2969fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2970    init_test(cx, |_| {});
 2971
 2972    let editor = cx.add_window(|window, cx| {
 2973        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 2974        build_editor(buffer, window, cx)
 2975    });
 2976    let del_to_prev_word_start = DeleteToPreviousWordStart {
 2977        ignore_newlines: false,
 2978        ignore_brackets: false,
 2979    };
 2980    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 2981        ignore_newlines: true,
 2982        ignore_brackets: false,
 2983    };
 2984
 2985    _ = editor.update(cx, |editor, window, cx| {
 2986        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2987            s.select_display_ranges([
 2988                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 2989            ])
 2990        });
 2991        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2992        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 2993        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2994        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 2995        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2996        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 2997        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2998        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 2999        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 3000        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 3001        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 3002        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3003    });
 3004}
 3005
 3006#[gpui::test]
 3007fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 3008    init_test(cx, |_| {});
 3009
 3010    let editor = cx.add_window(|window, cx| {
 3011        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 3012        build_editor(buffer, window, cx)
 3013    });
 3014    let del_to_next_word_end = DeleteToNextWordEnd {
 3015        ignore_newlines: false,
 3016        ignore_brackets: false,
 3017    };
 3018    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 3019        ignore_newlines: true,
 3020        ignore_brackets: false,
 3021    };
 3022
 3023    _ = editor.update(cx, |editor, window, cx| {
 3024        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3025            s.select_display_ranges([
 3026                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 3027            ])
 3028        });
 3029        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3030        assert_eq!(
 3031            editor.buffer.read(cx).read(cx).text(),
 3032            "one\n   two\nthree\n   four"
 3033        );
 3034        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3035        assert_eq!(
 3036            editor.buffer.read(cx).read(cx).text(),
 3037            "\n   two\nthree\n   four"
 3038        );
 3039        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3040        assert_eq!(
 3041            editor.buffer.read(cx).read(cx).text(),
 3042            "two\nthree\n   four"
 3043        );
 3044        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3045        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 3046        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3047        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 3048        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3049        assert_eq!(editor.buffer.read(cx).read(cx).text(), "four");
 3050        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3051        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3052    });
 3053}
 3054
 3055#[gpui::test]
 3056fn test_newline(cx: &mut TestAppContext) {
 3057    init_test(cx, |_| {});
 3058
 3059    let editor = cx.add_window(|window, cx| {
 3060        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 3061        build_editor(buffer, window, cx)
 3062    });
 3063
 3064    _ = editor.update(cx, |editor, window, cx| {
 3065        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3066            s.select_display_ranges([
 3067                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 3068                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 3069                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 3070            ])
 3071        });
 3072
 3073        editor.newline(&Newline, window, cx);
 3074        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 3075    });
 3076}
 3077
 3078#[gpui::test]
 3079async fn test_newline_yaml(cx: &mut TestAppContext) {
 3080    init_test(cx, |_| {});
 3081
 3082    let mut cx = EditorTestContext::new(cx).await;
 3083    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3084    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3085
 3086    // Object (between 2 fields)
 3087    cx.set_state(indoc! {"
 3088    test:ˇ
 3089    hello: bye"});
 3090    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3091    cx.assert_editor_state(indoc! {"
 3092    test:
 3093        ˇ
 3094    hello: bye"});
 3095
 3096    // Object (first and single line)
 3097    cx.set_state(indoc! {"
 3098    test:ˇ"});
 3099    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3100    cx.assert_editor_state(indoc! {"
 3101    test:
 3102        ˇ"});
 3103
 3104    // Array with objects (after first element)
 3105    cx.set_state(indoc! {"
 3106    test:
 3107        - foo: barˇ"});
 3108    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3109    cx.assert_editor_state(indoc! {"
 3110    test:
 3111        - foo: bar
 3112        ˇ"});
 3113
 3114    // Array with objects and comment
 3115    cx.set_state(indoc! {"
 3116    test:
 3117        - foo: bar
 3118        - bar: # testˇ"});
 3119    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3120    cx.assert_editor_state(indoc! {"
 3121    test:
 3122        - foo: bar
 3123        - bar: # test
 3124            ˇ"});
 3125
 3126    // Array with objects (after second element)
 3127    cx.set_state(indoc! {"
 3128    test:
 3129        - foo: bar
 3130        - bar: fooˇ"});
 3131    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3132    cx.assert_editor_state(indoc! {"
 3133    test:
 3134        - foo: bar
 3135        - bar: foo
 3136        ˇ"});
 3137
 3138    // Array with strings (after first element)
 3139    cx.set_state(indoc! {"
 3140    test:
 3141        - fooˇ"});
 3142    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3143    cx.assert_editor_state(indoc! {"
 3144    test:
 3145        - foo
 3146        ˇ"});
 3147}
 3148
 3149#[gpui::test]
 3150fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 3151    init_test(cx, |_| {});
 3152
 3153    let editor = cx.add_window(|window, cx| {
 3154        let buffer = MultiBuffer::build_simple(
 3155            "
 3156                a
 3157                b(
 3158                    X
 3159                )
 3160                c(
 3161                    X
 3162                )
 3163            "
 3164            .unindent()
 3165            .as_str(),
 3166            cx,
 3167        );
 3168        let mut editor = build_editor(buffer, window, cx);
 3169        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3170            s.select_ranges([
 3171                Point::new(2, 4)..Point::new(2, 5),
 3172                Point::new(5, 4)..Point::new(5, 5),
 3173            ])
 3174        });
 3175        editor
 3176    });
 3177
 3178    _ = editor.update(cx, |editor, window, cx| {
 3179        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3180        editor.buffer.update(cx, |buffer, cx| {
 3181            buffer.edit(
 3182                [
 3183                    (Point::new(1, 2)..Point::new(3, 0), ""),
 3184                    (Point::new(4, 2)..Point::new(6, 0), ""),
 3185                ],
 3186                None,
 3187                cx,
 3188            );
 3189            assert_eq!(
 3190                buffer.read(cx).text(),
 3191                "
 3192                    a
 3193                    b()
 3194                    c()
 3195                "
 3196                .unindent()
 3197            );
 3198        });
 3199        assert_eq!(
 3200            editor.selections.ranges(&editor.display_snapshot(cx)),
 3201            &[
 3202                Point::new(1, 2)..Point::new(1, 2),
 3203                Point::new(2, 2)..Point::new(2, 2),
 3204            ],
 3205        );
 3206
 3207        editor.newline(&Newline, window, cx);
 3208        assert_eq!(
 3209            editor.text(cx),
 3210            "
 3211                a
 3212                b(
 3213                )
 3214                c(
 3215                )
 3216            "
 3217            .unindent()
 3218        );
 3219
 3220        // The selections are moved after the inserted newlines
 3221        assert_eq!(
 3222            editor.selections.ranges(&editor.display_snapshot(cx)),
 3223            &[
 3224                Point::new(2, 0)..Point::new(2, 0),
 3225                Point::new(4, 0)..Point::new(4, 0),
 3226            ],
 3227        );
 3228    });
 3229}
 3230
 3231#[gpui::test]
 3232async fn test_newline_above(cx: &mut TestAppContext) {
 3233    init_test(cx, |settings| {
 3234        settings.defaults.tab_size = NonZeroU32::new(4)
 3235    });
 3236
 3237    let language = Arc::new(
 3238        Language::new(
 3239            LanguageConfig::default(),
 3240            Some(tree_sitter_rust::LANGUAGE.into()),
 3241        )
 3242        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3243        .unwrap(),
 3244    );
 3245
 3246    let mut cx = EditorTestContext::new(cx).await;
 3247    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3248    cx.set_state(indoc! {"
 3249        const a: ˇA = (
 3250 3251                «const_functionˇ»(ˇ),
 3252                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3253 3254        ˇ);ˇ
 3255    "});
 3256
 3257    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 3258    cx.assert_editor_state(indoc! {"
 3259        ˇ
 3260        const a: A = (
 3261            ˇ
 3262            (
 3263                ˇ
 3264                ˇ
 3265                const_function(),
 3266                ˇ
 3267                ˇ
 3268                ˇ
 3269                ˇ
 3270                something_else,
 3271                ˇ
 3272            )
 3273            ˇ
 3274            ˇ
 3275        );
 3276    "});
 3277}
 3278
 3279#[gpui::test]
 3280async fn test_newline_below(cx: &mut TestAppContext) {
 3281    init_test(cx, |settings| {
 3282        settings.defaults.tab_size = NonZeroU32::new(4)
 3283    });
 3284
 3285    let language = Arc::new(
 3286        Language::new(
 3287            LanguageConfig::default(),
 3288            Some(tree_sitter_rust::LANGUAGE.into()),
 3289        )
 3290        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3291        .unwrap(),
 3292    );
 3293
 3294    let mut cx = EditorTestContext::new(cx).await;
 3295    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3296    cx.set_state(indoc! {"
 3297        const a: ˇA = (
 3298 3299                «const_functionˇ»(ˇ),
 3300                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3301 3302        ˇ);ˇ
 3303    "});
 3304
 3305    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 3306    cx.assert_editor_state(indoc! {"
 3307        const a: A = (
 3308            ˇ
 3309            (
 3310                ˇ
 3311                const_function(),
 3312                ˇ
 3313                ˇ
 3314                something_else,
 3315                ˇ
 3316                ˇ
 3317                ˇ
 3318                ˇ
 3319            )
 3320            ˇ
 3321        );
 3322        ˇ
 3323        ˇ
 3324    "});
 3325}
 3326
 3327#[gpui::test]
 3328async fn test_newline_comments(cx: &mut TestAppContext) {
 3329    init_test(cx, |settings| {
 3330        settings.defaults.tab_size = NonZeroU32::new(4)
 3331    });
 3332
 3333    let language = Arc::new(Language::new(
 3334        LanguageConfig {
 3335            line_comments: vec!["// ".into()],
 3336            ..LanguageConfig::default()
 3337        },
 3338        None,
 3339    ));
 3340    {
 3341        let mut cx = EditorTestContext::new(cx).await;
 3342        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3343        cx.set_state(indoc! {"
 3344        // Fooˇ
 3345    "});
 3346
 3347        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3348        cx.assert_editor_state(indoc! {"
 3349        // Foo
 3350        // ˇ
 3351    "});
 3352        // Ensure that we add comment prefix when existing line contains space
 3353        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3354        cx.assert_editor_state(
 3355            indoc! {"
 3356        // Foo
 3357        //s
 3358        // ˇ
 3359    "}
 3360            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3361            .as_str(),
 3362        );
 3363        // Ensure that we add comment prefix when existing line does not contain space
 3364        cx.set_state(indoc! {"
 3365        // Foo
 3366        //ˇ
 3367    "});
 3368        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3369        cx.assert_editor_state(indoc! {"
 3370        // Foo
 3371        //
 3372        // ˇ
 3373    "});
 3374        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 3375        cx.set_state(indoc! {"
 3376        ˇ// Foo
 3377    "});
 3378        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3379        cx.assert_editor_state(indoc! {"
 3380
 3381        ˇ// Foo
 3382    "});
 3383    }
 3384    // Ensure that comment continuations can be disabled.
 3385    update_test_language_settings(cx, |settings| {
 3386        settings.defaults.extend_comment_on_newline = Some(false);
 3387    });
 3388    let mut cx = EditorTestContext::new(cx).await;
 3389    cx.set_state(indoc! {"
 3390        // Fooˇ
 3391    "});
 3392    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3393    cx.assert_editor_state(indoc! {"
 3394        // Foo
 3395        ˇ
 3396    "});
 3397}
 3398
 3399#[gpui::test]
 3400async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 3401    init_test(cx, |settings| {
 3402        settings.defaults.tab_size = NonZeroU32::new(4)
 3403    });
 3404
 3405    let language = Arc::new(Language::new(
 3406        LanguageConfig {
 3407            line_comments: vec!["// ".into(), "/// ".into()],
 3408            ..LanguageConfig::default()
 3409        },
 3410        None,
 3411    ));
 3412    {
 3413        let mut cx = EditorTestContext::new(cx).await;
 3414        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3415        cx.set_state(indoc! {"
 3416        //ˇ
 3417    "});
 3418        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3419        cx.assert_editor_state(indoc! {"
 3420        //
 3421        // ˇ
 3422    "});
 3423
 3424        cx.set_state(indoc! {"
 3425        ///ˇ
 3426    "});
 3427        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3428        cx.assert_editor_state(indoc! {"
 3429        ///
 3430        /// ˇ
 3431    "});
 3432    }
 3433}
 3434
 3435#[gpui::test]
 3436async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 3437    init_test(cx, |settings| {
 3438        settings.defaults.tab_size = NonZeroU32::new(4)
 3439    });
 3440
 3441    let language = Arc::new(
 3442        Language::new(
 3443            LanguageConfig {
 3444                documentation_comment: Some(language::BlockCommentConfig {
 3445                    start: "/**".into(),
 3446                    end: "*/".into(),
 3447                    prefix: "* ".into(),
 3448                    tab_size: 1,
 3449                }),
 3450
 3451                ..LanguageConfig::default()
 3452            },
 3453            Some(tree_sitter_rust::LANGUAGE.into()),
 3454        )
 3455        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 3456        .unwrap(),
 3457    );
 3458
 3459    {
 3460        let mut cx = EditorTestContext::new(cx).await;
 3461        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3462        cx.set_state(indoc! {"
 3463        /**ˇ
 3464    "});
 3465
 3466        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3467        cx.assert_editor_state(indoc! {"
 3468        /**
 3469         * ˇ
 3470    "});
 3471        // Ensure that if cursor is before the comment start,
 3472        // we do not actually insert a comment prefix.
 3473        cx.set_state(indoc! {"
 3474        ˇ/**
 3475    "});
 3476        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3477        cx.assert_editor_state(indoc! {"
 3478
 3479        ˇ/**
 3480    "});
 3481        // Ensure that if cursor is between it doesn't add comment prefix.
 3482        cx.set_state(indoc! {"
 3483        /*ˇ*
 3484    "});
 3485        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3486        cx.assert_editor_state(indoc! {"
 3487        /*
 3488        ˇ*
 3489    "});
 3490        // Ensure that if suffix exists on same line after cursor it adds new line.
 3491        cx.set_state(indoc! {"
 3492        /**ˇ*/
 3493    "});
 3494        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3495        cx.assert_editor_state(indoc! {"
 3496        /**
 3497         * ˇ
 3498         */
 3499    "});
 3500        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3501        cx.set_state(indoc! {"
 3502        /**ˇ */
 3503    "});
 3504        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3505        cx.assert_editor_state(indoc! {"
 3506        /**
 3507         * ˇ
 3508         */
 3509    "});
 3510        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3511        cx.set_state(indoc! {"
 3512        /** ˇ*/
 3513    "});
 3514        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3515        cx.assert_editor_state(
 3516            indoc! {"
 3517        /**s
 3518         * ˇ
 3519         */
 3520    "}
 3521            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3522            .as_str(),
 3523        );
 3524        // Ensure that delimiter space is preserved when newline on already
 3525        // spaced delimiter.
 3526        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3527        cx.assert_editor_state(
 3528            indoc! {"
 3529        /**s
 3530         *s
 3531         * ˇ
 3532         */
 3533    "}
 3534            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3535            .as_str(),
 3536        );
 3537        // Ensure that delimiter space is preserved when space is not
 3538        // on existing delimiter.
 3539        cx.set_state(indoc! {"
 3540        /**
 3541 3542         */
 3543    "});
 3544        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3545        cx.assert_editor_state(indoc! {"
 3546        /**
 3547         *
 3548         * ˇ
 3549         */
 3550    "});
 3551        // Ensure that if suffix exists on same line after cursor it
 3552        // doesn't add extra new line if prefix is not on same line.
 3553        cx.set_state(indoc! {"
 3554        /**
 3555        ˇ*/
 3556    "});
 3557        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3558        cx.assert_editor_state(indoc! {"
 3559        /**
 3560
 3561        ˇ*/
 3562    "});
 3563        // Ensure that it detects suffix after existing prefix.
 3564        cx.set_state(indoc! {"
 3565        /**ˇ/
 3566    "});
 3567        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3568        cx.assert_editor_state(indoc! {"
 3569        /**
 3570        ˇ/
 3571    "});
 3572        // Ensure that if suffix exists on same line before
 3573        // cursor it does not add comment prefix.
 3574        cx.set_state(indoc! {"
 3575        /** */ˇ
 3576    "});
 3577        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3578        cx.assert_editor_state(indoc! {"
 3579        /** */
 3580        ˇ
 3581    "});
 3582        // Ensure that if suffix exists on same line before
 3583        // cursor it does not add comment prefix.
 3584        cx.set_state(indoc! {"
 3585        /**
 3586         *
 3587         */ˇ
 3588    "});
 3589        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3590        cx.assert_editor_state(indoc! {"
 3591        /**
 3592         *
 3593         */
 3594         ˇ
 3595    "});
 3596
 3597        // Ensure that inline comment followed by code
 3598        // doesn't add comment prefix on newline
 3599        cx.set_state(indoc! {"
 3600        /** */ textˇ
 3601    "});
 3602        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3603        cx.assert_editor_state(indoc! {"
 3604        /** */ text
 3605        ˇ
 3606    "});
 3607
 3608        // Ensure that text after comment end tag
 3609        // doesn't add comment prefix on newline
 3610        cx.set_state(indoc! {"
 3611        /**
 3612         *
 3613         */ˇtext
 3614    "});
 3615        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3616        cx.assert_editor_state(indoc! {"
 3617        /**
 3618         *
 3619         */
 3620         ˇtext
 3621    "});
 3622
 3623        // Ensure if not comment block it doesn't
 3624        // add comment prefix on newline
 3625        cx.set_state(indoc! {"
 3626        * textˇ
 3627    "});
 3628        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3629        cx.assert_editor_state(indoc! {"
 3630        * text
 3631        ˇ
 3632    "});
 3633    }
 3634    // Ensure that comment continuations can be disabled.
 3635    update_test_language_settings(cx, |settings| {
 3636        settings.defaults.extend_comment_on_newline = Some(false);
 3637    });
 3638    let mut cx = EditorTestContext::new(cx).await;
 3639    cx.set_state(indoc! {"
 3640        /**ˇ
 3641    "});
 3642    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3643    cx.assert_editor_state(indoc! {"
 3644        /**
 3645        ˇ
 3646    "});
 3647}
 3648
 3649#[gpui::test]
 3650async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
 3651    init_test(cx, |settings| {
 3652        settings.defaults.tab_size = NonZeroU32::new(4)
 3653    });
 3654
 3655    let lua_language = Arc::new(Language::new(
 3656        LanguageConfig {
 3657            line_comments: vec!["--".into()],
 3658            block_comment: Some(language::BlockCommentConfig {
 3659                start: "--[[".into(),
 3660                prefix: "".into(),
 3661                end: "]]".into(),
 3662                tab_size: 0,
 3663            }),
 3664            ..LanguageConfig::default()
 3665        },
 3666        None,
 3667    ));
 3668
 3669    let mut cx = EditorTestContext::new(cx).await;
 3670    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 3671
 3672    // Line with line comment should extend
 3673    cx.set_state(indoc! {"
 3674        --ˇ
 3675    "});
 3676    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3677    cx.assert_editor_state(indoc! {"
 3678        --
 3679        --ˇ
 3680    "});
 3681
 3682    // Line with block comment that matches line comment should not extend
 3683    cx.set_state(indoc! {"
 3684        --[[ˇ
 3685    "});
 3686    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3687    cx.assert_editor_state(indoc! {"
 3688        --[[
 3689        ˇ
 3690    "});
 3691}
 3692
 3693#[gpui::test]
 3694fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3695    init_test(cx, |_| {});
 3696
 3697    let editor = cx.add_window(|window, cx| {
 3698        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3699        let mut editor = build_editor(buffer, window, cx);
 3700        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3701            s.select_ranges([3..4, 11..12, 19..20])
 3702        });
 3703        editor
 3704    });
 3705
 3706    _ = editor.update(cx, |editor, window, cx| {
 3707        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3708        editor.buffer.update(cx, |buffer, cx| {
 3709            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 3710            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3711        });
 3712        assert_eq!(
 3713            editor.selections.ranges(&editor.display_snapshot(cx)),
 3714            &[2..2, 7..7, 12..12],
 3715        );
 3716
 3717        editor.insert("Z", window, cx);
 3718        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3719
 3720        // The selections are moved after the inserted characters
 3721        assert_eq!(
 3722            editor.selections.ranges(&editor.display_snapshot(cx)),
 3723            &[3..3, 9..9, 15..15],
 3724        );
 3725    });
 3726}
 3727
 3728#[gpui::test]
 3729async fn test_tab(cx: &mut TestAppContext) {
 3730    init_test(cx, |settings| {
 3731        settings.defaults.tab_size = NonZeroU32::new(3)
 3732    });
 3733
 3734    let mut cx = EditorTestContext::new(cx).await;
 3735    cx.set_state(indoc! {"
 3736        ˇabˇc
 3737        ˇ🏀ˇ🏀ˇefg
 3738 3739    "});
 3740    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3741    cx.assert_editor_state(indoc! {"
 3742           ˇab ˇc
 3743           ˇ🏀  ˇ🏀  ˇefg
 3744        d  ˇ
 3745    "});
 3746
 3747    cx.set_state(indoc! {"
 3748        a
 3749        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3750    "});
 3751    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3752    cx.assert_editor_state(indoc! {"
 3753        a
 3754           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3755    "});
 3756}
 3757
 3758#[gpui::test]
 3759async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3760    init_test(cx, |_| {});
 3761
 3762    let mut cx = EditorTestContext::new(cx).await;
 3763    let language = Arc::new(
 3764        Language::new(
 3765            LanguageConfig::default(),
 3766            Some(tree_sitter_rust::LANGUAGE.into()),
 3767        )
 3768        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3769        .unwrap(),
 3770    );
 3771    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3772
 3773    // test when all cursors are not at suggested indent
 3774    // then simply move to their suggested indent location
 3775    cx.set_state(indoc! {"
 3776        const a: B = (
 3777            c(
 3778        ˇ
 3779        ˇ    )
 3780        );
 3781    "});
 3782    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3783    cx.assert_editor_state(indoc! {"
 3784        const a: B = (
 3785            c(
 3786                ˇ
 3787            ˇ)
 3788        );
 3789    "});
 3790
 3791    // test cursor already at suggested indent not moving when
 3792    // other cursors are yet to reach their suggested indents
 3793    cx.set_state(indoc! {"
 3794        ˇ
 3795        const a: B = (
 3796            c(
 3797                d(
 3798        ˇ
 3799                )
 3800        ˇ
 3801        ˇ    )
 3802        );
 3803    "});
 3804    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3805    cx.assert_editor_state(indoc! {"
 3806        ˇ
 3807        const a: B = (
 3808            c(
 3809                d(
 3810                    ˇ
 3811                )
 3812                ˇ
 3813            ˇ)
 3814        );
 3815    "});
 3816    // test when all cursors are at suggested indent then tab is inserted
 3817    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3818    cx.assert_editor_state(indoc! {"
 3819            ˇ
 3820        const a: B = (
 3821            c(
 3822                d(
 3823                        ˇ
 3824                )
 3825                    ˇ
 3826                ˇ)
 3827        );
 3828    "});
 3829
 3830    // test when current indent is less than suggested indent,
 3831    // we adjust line to match suggested indent and move cursor to it
 3832    //
 3833    // when no other cursor is at word boundary, all of them should move
 3834    cx.set_state(indoc! {"
 3835        const a: B = (
 3836            c(
 3837                d(
 3838        ˇ
 3839        ˇ   )
 3840        ˇ   )
 3841        );
 3842    "});
 3843    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3844    cx.assert_editor_state(indoc! {"
 3845        const a: B = (
 3846            c(
 3847                d(
 3848                    ˇ
 3849                ˇ)
 3850            ˇ)
 3851        );
 3852    "});
 3853
 3854    // test when current indent is less than suggested indent,
 3855    // we adjust line to match suggested indent and move cursor to it
 3856    //
 3857    // when some other cursor is at word boundary, it should not move
 3858    cx.set_state(indoc! {"
 3859        const a: B = (
 3860            c(
 3861                d(
 3862        ˇ
 3863        ˇ   )
 3864           ˇ)
 3865        );
 3866    "});
 3867    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3868    cx.assert_editor_state(indoc! {"
 3869        const a: B = (
 3870            c(
 3871                d(
 3872                    ˇ
 3873                ˇ)
 3874            ˇ)
 3875        );
 3876    "});
 3877
 3878    // test when current indent is more than suggested indent,
 3879    // we just move cursor to current indent instead of suggested indent
 3880    //
 3881    // when no other cursor is at word boundary, all of them should move
 3882    cx.set_state(indoc! {"
 3883        const a: B = (
 3884            c(
 3885                d(
 3886        ˇ
 3887        ˇ                )
 3888        ˇ   )
 3889        );
 3890    "});
 3891    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3892    cx.assert_editor_state(indoc! {"
 3893        const a: B = (
 3894            c(
 3895                d(
 3896                    ˇ
 3897                        ˇ)
 3898            ˇ)
 3899        );
 3900    "});
 3901    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3902    cx.assert_editor_state(indoc! {"
 3903        const a: B = (
 3904            c(
 3905                d(
 3906                        ˇ
 3907                            ˇ)
 3908                ˇ)
 3909        );
 3910    "});
 3911
 3912    // test when current indent is more than suggested indent,
 3913    // we just move cursor to current indent instead of suggested indent
 3914    //
 3915    // when some other cursor is at word boundary, it doesn't move
 3916    cx.set_state(indoc! {"
 3917        const a: B = (
 3918            c(
 3919                d(
 3920        ˇ
 3921        ˇ                )
 3922            ˇ)
 3923        );
 3924    "});
 3925    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3926    cx.assert_editor_state(indoc! {"
 3927        const a: B = (
 3928            c(
 3929                d(
 3930                    ˇ
 3931                        ˇ)
 3932            ˇ)
 3933        );
 3934    "});
 3935
 3936    // handle auto-indent when there are multiple cursors on the same line
 3937    cx.set_state(indoc! {"
 3938        const a: B = (
 3939            c(
 3940        ˇ    ˇ
 3941        ˇ    )
 3942        );
 3943    "});
 3944    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3945    cx.assert_editor_state(indoc! {"
 3946        const a: B = (
 3947            c(
 3948                ˇ
 3949            ˇ)
 3950        );
 3951    "});
 3952}
 3953
 3954#[gpui::test]
 3955async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 3956    init_test(cx, |settings| {
 3957        settings.defaults.tab_size = NonZeroU32::new(3)
 3958    });
 3959
 3960    let mut cx = EditorTestContext::new(cx).await;
 3961    cx.set_state(indoc! {"
 3962         ˇ
 3963        \t ˇ
 3964        \t  ˇ
 3965        \t   ˇ
 3966         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 3967    "});
 3968
 3969    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3970    cx.assert_editor_state(indoc! {"
 3971           ˇ
 3972        \t   ˇ
 3973        \t   ˇ
 3974        \t      ˇ
 3975         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 3976    "});
 3977}
 3978
 3979#[gpui::test]
 3980async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 3981    init_test(cx, |settings| {
 3982        settings.defaults.tab_size = NonZeroU32::new(4)
 3983    });
 3984
 3985    let language = Arc::new(
 3986        Language::new(
 3987            LanguageConfig::default(),
 3988            Some(tree_sitter_rust::LANGUAGE.into()),
 3989        )
 3990        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 3991        .unwrap(),
 3992    );
 3993
 3994    let mut cx = EditorTestContext::new(cx).await;
 3995    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3996    cx.set_state(indoc! {"
 3997        fn a() {
 3998            if b {
 3999        \t ˇc
 4000            }
 4001        }
 4002    "});
 4003
 4004    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4005    cx.assert_editor_state(indoc! {"
 4006        fn a() {
 4007            if b {
 4008                ˇc
 4009            }
 4010        }
 4011    "});
 4012}
 4013
 4014#[gpui::test]
 4015async fn test_indent_outdent(cx: &mut TestAppContext) {
 4016    init_test(cx, |settings| {
 4017        settings.defaults.tab_size = NonZeroU32::new(4);
 4018    });
 4019
 4020    let mut cx = EditorTestContext::new(cx).await;
 4021
 4022    cx.set_state(indoc! {"
 4023          «oneˇ» «twoˇ»
 4024        three
 4025         four
 4026    "});
 4027    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4028    cx.assert_editor_state(indoc! {"
 4029            «oneˇ» «twoˇ»
 4030        three
 4031         four
 4032    "});
 4033
 4034    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4035    cx.assert_editor_state(indoc! {"
 4036        «oneˇ» «twoˇ»
 4037        three
 4038         four
 4039    "});
 4040
 4041    // select across line ending
 4042    cx.set_state(indoc! {"
 4043        one two
 4044        t«hree
 4045        ˇ» four
 4046    "});
 4047    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4048    cx.assert_editor_state(indoc! {"
 4049        one two
 4050            t«hree
 4051        ˇ» four
 4052    "});
 4053
 4054    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4055    cx.assert_editor_state(indoc! {"
 4056        one two
 4057        t«hree
 4058        ˇ» four
 4059    "});
 4060
 4061    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4062    cx.set_state(indoc! {"
 4063        one two
 4064        ˇthree
 4065            four
 4066    "});
 4067    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4068    cx.assert_editor_state(indoc! {"
 4069        one two
 4070            ˇthree
 4071            four
 4072    "});
 4073
 4074    cx.set_state(indoc! {"
 4075        one two
 4076        ˇ    three
 4077            four
 4078    "});
 4079    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4080    cx.assert_editor_state(indoc! {"
 4081        one two
 4082        ˇthree
 4083            four
 4084    "});
 4085}
 4086
 4087#[gpui::test]
 4088async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 4089    // This is a regression test for issue #33761
 4090    init_test(cx, |_| {});
 4091
 4092    let mut cx = EditorTestContext::new(cx).await;
 4093    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 4094    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 4095
 4096    cx.set_state(
 4097        r#"ˇ#     ingress:
 4098ˇ#         api:
 4099ˇ#             enabled: false
 4100ˇ#             pathType: Prefix
 4101ˇ#           console:
 4102ˇ#               enabled: false
 4103ˇ#               pathType: Prefix
 4104"#,
 4105    );
 4106
 4107    // Press tab to indent all lines
 4108    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4109
 4110    cx.assert_editor_state(
 4111        r#"    ˇ#     ingress:
 4112    ˇ#         api:
 4113    ˇ#             enabled: false
 4114    ˇ#             pathType: Prefix
 4115    ˇ#           console:
 4116    ˇ#               enabled: false
 4117    ˇ#               pathType: Prefix
 4118"#,
 4119    );
 4120}
 4121
 4122#[gpui::test]
 4123async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 4124    // This is a test to make sure our fix for issue #33761 didn't break anything
 4125    init_test(cx, |_| {});
 4126
 4127    let mut cx = EditorTestContext::new(cx).await;
 4128    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 4129    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 4130
 4131    cx.set_state(
 4132        r#"ˇingress:
 4133ˇ  api:
 4134ˇ    enabled: false
 4135ˇ    pathType: Prefix
 4136"#,
 4137    );
 4138
 4139    // Press tab to indent all lines
 4140    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4141
 4142    cx.assert_editor_state(
 4143        r#"ˇingress:
 4144    ˇapi:
 4145        ˇenabled: false
 4146        ˇpathType: Prefix
 4147"#,
 4148    );
 4149}
 4150
 4151#[gpui::test]
 4152async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 4153    init_test(cx, |settings| {
 4154        settings.defaults.hard_tabs = Some(true);
 4155    });
 4156
 4157    let mut cx = EditorTestContext::new(cx).await;
 4158
 4159    // select two ranges on one line
 4160    cx.set_state(indoc! {"
 4161        «oneˇ» «twoˇ»
 4162        three
 4163        four
 4164    "});
 4165    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4166    cx.assert_editor_state(indoc! {"
 4167        \t«oneˇ» «twoˇ»
 4168        three
 4169        four
 4170    "});
 4171    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4172    cx.assert_editor_state(indoc! {"
 4173        \t\t«oneˇ» «twoˇ»
 4174        three
 4175        four
 4176    "});
 4177    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4178    cx.assert_editor_state(indoc! {"
 4179        \t«oneˇ» «twoˇ»
 4180        three
 4181        four
 4182    "});
 4183    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4184    cx.assert_editor_state(indoc! {"
 4185        «oneˇ» «twoˇ»
 4186        three
 4187        four
 4188    "});
 4189
 4190    // select across a line ending
 4191    cx.set_state(indoc! {"
 4192        one two
 4193        t«hree
 4194        ˇ»four
 4195    "});
 4196    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4197    cx.assert_editor_state(indoc! {"
 4198        one two
 4199        \tt«hree
 4200        ˇ»four
 4201    "});
 4202    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4203    cx.assert_editor_state(indoc! {"
 4204        one two
 4205        \t\tt«hree
 4206        ˇ»four
 4207    "});
 4208    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4209    cx.assert_editor_state(indoc! {"
 4210        one two
 4211        \tt«hree
 4212        ˇ»four
 4213    "});
 4214    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4215    cx.assert_editor_state(indoc! {"
 4216        one two
 4217        t«hree
 4218        ˇ»four
 4219    "});
 4220
 4221    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4222    cx.set_state(indoc! {"
 4223        one two
 4224        ˇthree
 4225        four
 4226    "});
 4227    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4228    cx.assert_editor_state(indoc! {"
 4229        one two
 4230        ˇthree
 4231        four
 4232    "});
 4233    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4234    cx.assert_editor_state(indoc! {"
 4235        one two
 4236        \tˇthree
 4237        four
 4238    "});
 4239    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4240    cx.assert_editor_state(indoc! {"
 4241        one two
 4242        ˇthree
 4243        four
 4244    "});
 4245}
 4246
 4247#[gpui::test]
 4248fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 4249    init_test(cx, |settings| {
 4250        settings.languages.0.extend([
 4251            (
 4252                "TOML".into(),
 4253                LanguageSettingsContent {
 4254                    tab_size: NonZeroU32::new(2),
 4255                    ..Default::default()
 4256                },
 4257            ),
 4258            (
 4259                "Rust".into(),
 4260                LanguageSettingsContent {
 4261                    tab_size: NonZeroU32::new(4),
 4262                    ..Default::default()
 4263                },
 4264            ),
 4265        ]);
 4266    });
 4267
 4268    let toml_language = Arc::new(Language::new(
 4269        LanguageConfig {
 4270            name: "TOML".into(),
 4271            ..Default::default()
 4272        },
 4273        None,
 4274    ));
 4275    let rust_language = Arc::new(Language::new(
 4276        LanguageConfig {
 4277            name: "Rust".into(),
 4278            ..Default::default()
 4279        },
 4280        None,
 4281    ));
 4282
 4283    let toml_buffer =
 4284        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 4285    let rust_buffer =
 4286        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 4287    let multibuffer = cx.new(|cx| {
 4288        let mut multibuffer = MultiBuffer::new(ReadWrite);
 4289        multibuffer.push_excerpts(
 4290            toml_buffer.clone(),
 4291            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 4292            cx,
 4293        );
 4294        multibuffer.push_excerpts(
 4295            rust_buffer.clone(),
 4296            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 4297            cx,
 4298        );
 4299        multibuffer
 4300    });
 4301
 4302    cx.add_window(|window, cx| {
 4303        let mut editor = build_editor(multibuffer, window, cx);
 4304
 4305        assert_eq!(
 4306            editor.text(cx),
 4307            indoc! {"
 4308                a = 1
 4309                b = 2
 4310
 4311                const c: usize = 3;
 4312            "}
 4313        );
 4314
 4315        select_ranges(
 4316            &mut editor,
 4317            indoc! {"
 4318                «aˇ» = 1
 4319                b = 2
 4320
 4321                «const c:ˇ» usize = 3;
 4322            "},
 4323            window,
 4324            cx,
 4325        );
 4326
 4327        editor.tab(&Tab, window, cx);
 4328        assert_text_with_selections(
 4329            &mut editor,
 4330            indoc! {"
 4331                  «aˇ» = 1
 4332                b = 2
 4333
 4334                    «const c:ˇ» usize = 3;
 4335            "},
 4336            cx,
 4337        );
 4338        editor.backtab(&Backtab, window, cx);
 4339        assert_text_with_selections(
 4340            &mut editor,
 4341            indoc! {"
 4342                «aˇ» = 1
 4343                b = 2
 4344
 4345                «const c:ˇ» usize = 3;
 4346            "},
 4347            cx,
 4348        );
 4349
 4350        editor
 4351    });
 4352}
 4353
 4354#[gpui::test]
 4355async fn test_backspace(cx: &mut TestAppContext) {
 4356    init_test(cx, |_| {});
 4357
 4358    let mut cx = EditorTestContext::new(cx).await;
 4359
 4360    // Basic backspace
 4361    cx.set_state(indoc! {"
 4362        onˇe two three
 4363        fou«rˇ» five six
 4364        seven «ˇeight nine
 4365        »ten
 4366    "});
 4367    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4368    cx.assert_editor_state(indoc! {"
 4369        oˇe two three
 4370        fouˇ five six
 4371        seven ˇten
 4372    "});
 4373
 4374    // Test backspace inside and around indents
 4375    cx.set_state(indoc! {"
 4376        zero
 4377            ˇone
 4378                ˇtwo
 4379            ˇ ˇ ˇ  three
 4380        ˇ  ˇ  four
 4381    "});
 4382    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4383    cx.assert_editor_state(indoc! {"
 4384        zero
 4385        ˇone
 4386            ˇtwo
 4387        ˇ  threeˇ  four
 4388    "});
 4389}
 4390
 4391#[gpui::test]
 4392async fn test_delete(cx: &mut TestAppContext) {
 4393    init_test(cx, |_| {});
 4394
 4395    let mut cx = EditorTestContext::new(cx).await;
 4396    cx.set_state(indoc! {"
 4397        onˇe two three
 4398        fou«rˇ» five six
 4399        seven «ˇeight nine
 4400        »ten
 4401    "});
 4402    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 4403    cx.assert_editor_state(indoc! {"
 4404        onˇ two three
 4405        fouˇ five six
 4406        seven ˇten
 4407    "});
 4408}
 4409
 4410#[gpui::test]
 4411fn test_delete_line(cx: &mut TestAppContext) {
 4412    init_test(cx, |_| {});
 4413
 4414    let editor = cx.add_window(|window, cx| {
 4415        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4416        build_editor(buffer, window, cx)
 4417    });
 4418    _ = editor.update(cx, |editor, window, cx| {
 4419        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4420            s.select_display_ranges([
 4421                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4422                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4423                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4424            ])
 4425        });
 4426        editor.delete_line(&DeleteLine, window, cx);
 4427        assert_eq!(editor.display_text(cx), "ghi");
 4428        assert_eq!(
 4429            display_ranges(editor, cx),
 4430            vec![
 4431                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 4432                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4433            ]
 4434        );
 4435    });
 4436
 4437    let editor = cx.add_window(|window, cx| {
 4438        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4439        build_editor(buffer, window, cx)
 4440    });
 4441    _ = editor.update(cx, |editor, window, cx| {
 4442        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4443            s.select_display_ranges([
 4444                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 4445            ])
 4446        });
 4447        editor.delete_line(&DeleteLine, window, cx);
 4448        assert_eq!(editor.display_text(cx), "ghi\n");
 4449        assert_eq!(
 4450            display_ranges(editor, cx),
 4451            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 4452        );
 4453    });
 4454
 4455    let editor = cx.add_window(|window, cx| {
 4456        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n\njkl\nmno", cx);
 4457        build_editor(buffer, window, cx)
 4458    });
 4459    _ = editor.update(cx, |editor, window, cx| {
 4460        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4461            s.select_display_ranges([
 4462                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(2), 1)
 4463            ])
 4464        });
 4465        editor.delete_line(&DeleteLine, window, cx);
 4466        assert_eq!(editor.display_text(cx), "\njkl\nmno");
 4467        assert_eq!(
 4468            display_ranges(editor, cx),
 4469            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 4470        );
 4471    });
 4472}
 4473
 4474#[gpui::test]
 4475fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 4476    init_test(cx, |_| {});
 4477
 4478    cx.add_window(|window, cx| {
 4479        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4480        let mut editor = build_editor(buffer.clone(), window, cx);
 4481        let buffer = buffer.read(cx).as_singleton().unwrap();
 4482
 4483        assert_eq!(
 4484            editor
 4485                .selections
 4486                .ranges::<Point>(&editor.display_snapshot(cx)),
 4487            &[Point::new(0, 0)..Point::new(0, 0)]
 4488        );
 4489
 4490        // When on single line, replace newline at end by space
 4491        editor.join_lines(&JoinLines, window, cx);
 4492        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4493        assert_eq!(
 4494            editor
 4495                .selections
 4496                .ranges::<Point>(&editor.display_snapshot(cx)),
 4497            &[Point::new(0, 3)..Point::new(0, 3)]
 4498        );
 4499
 4500        // When multiple lines are selected, remove newlines that are spanned by the selection
 4501        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4502            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 4503        });
 4504        editor.join_lines(&JoinLines, window, cx);
 4505        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 4506        assert_eq!(
 4507            editor
 4508                .selections
 4509                .ranges::<Point>(&editor.display_snapshot(cx)),
 4510            &[Point::new(0, 11)..Point::new(0, 11)]
 4511        );
 4512
 4513        // Undo should be transactional
 4514        editor.undo(&Undo, window, cx);
 4515        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4516        assert_eq!(
 4517            editor
 4518                .selections
 4519                .ranges::<Point>(&editor.display_snapshot(cx)),
 4520            &[Point::new(0, 5)..Point::new(2, 2)]
 4521        );
 4522
 4523        // When joining an empty line don't insert a space
 4524        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4525            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 4526        });
 4527        editor.join_lines(&JoinLines, window, cx);
 4528        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 4529        assert_eq!(
 4530            editor
 4531                .selections
 4532                .ranges::<Point>(&editor.display_snapshot(cx)),
 4533            [Point::new(2, 3)..Point::new(2, 3)]
 4534        );
 4535
 4536        // We can remove trailing newlines
 4537        editor.join_lines(&JoinLines, window, cx);
 4538        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4539        assert_eq!(
 4540            editor
 4541                .selections
 4542                .ranges::<Point>(&editor.display_snapshot(cx)),
 4543            [Point::new(2, 3)..Point::new(2, 3)]
 4544        );
 4545
 4546        // We don't blow up on the last line
 4547        editor.join_lines(&JoinLines, window, cx);
 4548        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4549        assert_eq!(
 4550            editor
 4551                .selections
 4552                .ranges::<Point>(&editor.display_snapshot(cx)),
 4553            [Point::new(2, 3)..Point::new(2, 3)]
 4554        );
 4555
 4556        // reset to test indentation
 4557        editor.buffer.update(cx, |buffer, cx| {
 4558            buffer.edit(
 4559                [
 4560                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 4561                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 4562                ],
 4563                None,
 4564                cx,
 4565            )
 4566        });
 4567
 4568        // We remove any leading spaces
 4569        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 4570        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4571            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 4572        });
 4573        editor.join_lines(&JoinLines, window, cx);
 4574        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 4575
 4576        // We don't insert a space for a line containing only spaces
 4577        editor.join_lines(&JoinLines, window, cx);
 4578        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 4579
 4580        // We ignore any leading tabs
 4581        editor.join_lines(&JoinLines, window, cx);
 4582        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 4583
 4584        editor
 4585    });
 4586}
 4587
 4588#[gpui::test]
 4589fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 4590    init_test(cx, |_| {});
 4591
 4592    cx.add_window(|window, cx| {
 4593        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4594        let mut editor = build_editor(buffer.clone(), window, cx);
 4595        let buffer = buffer.read(cx).as_singleton().unwrap();
 4596
 4597        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4598            s.select_ranges([
 4599                Point::new(0, 2)..Point::new(1, 1),
 4600                Point::new(1, 2)..Point::new(1, 2),
 4601                Point::new(3, 1)..Point::new(3, 2),
 4602            ])
 4603        });
 4604
 4605        editor.join_lines(&JoinLines, window, cx);
 4606        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 4607
 4608        assert_eq!(
 4609            editor
 4610                .selections
 4611                .ranges::<Point>(&editor.display_snapshot(cx)),
 4612            [
 4613                Point::new(0, 7)..Point::new(0, 7),
 4614                Point::new(1, 3)..Point::new(1, 3)
 4615            ]
 4616        );
 4617        editor
 4618    });
 4619}
 4620
 4621#[gpui::test]
 4622async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4623    init_test(cx, |_| {});
 4624
 4625    let mut cx = EditorTestContext::new(cx).await;
 4626
 4627    let diff_base = r#"
 4628        Line 0
 4629        Line 1
 4630        Line 2
 4631        Line 3
 4632        "#
 4633    .unindent();
 4634
 4635    cx.set_state(
 4636        &r#"
 4637        ˇLine 0
 4638        Line 1
 4639        Line 2
 4640        Line 3
 4641        "#
 4642        .unindent(),
 4643    );
 4644
 4645    cx.set_head_text(&diff_base);
 4646    executor.run_until_parked();
 4647
 4648    // Join lines
 4649    cx.update_editor(|editor, window, cx| {
 4650        editor.join_lines(&JoinLines, window, cx);
 4651    });
 4652    executor.run_until_parked();
 4653
 4654    cx.assert_editor_state(
 4655        &r#"
 4656        Line 0ˇ Line 1
 4657        Line 2
 4658        Line 3
 4659        "#
 4660        .unindent(),
 4661    );
 4662    // Join again
 4663    cx.update_editor(|editor, window, cx| {
 4664        editor.join_lines(&JoinLines, window, cx);
 4665    });
 4666    executor.run_until_parked();
 4667
 4668    cx.assert_editor_state(
 4669        &r#"
 4670        Line 0 Line 1ˇ Line 2
 4671        Line 3
 4672        "#
 4673        .unindent(),
 4674    );
 4675}
 4676
 4677#[gpui::test]
 4678async fn test_custom_newlines_cause_no_false_positive_diffs(
 4679    executor: BackgroundExecutor,
 4680    cx: &mut TestAppContext,
 4681) {
 4682    init_test(cx, |_| {});
 4683    let mut cx = EditorTestContext::new(cx).await;
 4684    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4685    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4686    executor.run_until_parked();
 4687
 4688    cx.update_editor(|editor, window, cx| {
 4689        let snapshot = editor.snapshot(window, cx);
 4690        assert_eq!(
 4691            snapshot
 4692                .buffer_snapshot()
 4693                .diff_hunks_in_range(0..snapshot.buffer_snapshot().len())
 4694                .collect::<Vec<_>>(),
 4695            Vec::new(),
 4696            "Should not have any diffs for files with custom newlines"
 4697        );
 4698    });
 4699}
 4700
 4701#[gpui::test]
 4702async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4703    init_test(cx, |_| {});
 4704
 4705    let mut cx = EditorTestContext::new(cx).await;
 4706
 4707    // Test sort_lines_case_insensitive()
 4708    cx.set_state(indoc! {"
 4709        «z
 4710        y
 4711        x
 4712        Z
 4713        Y
 4714        Xˇ»
 4715    "});
 4716    cx.update_editor(|e, window, cx| {
 4717        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4718    });
 4719    cx.assert_editor_state(indoc! {"
 4720        «x
 4721        X
 4722        y
 4723        Y
 4724        z
 4725        Zˇ»
 4726    "});
 4727
 4728    // Test sort_lines_by_length()
 4729    //
 4730    // Demonstrates:
 4731    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4732    // - sort is stable
 4733    cx.set_state(indoc! {"
 4734        «123
 4735        æ
 4736        12
 4737 4738        1
 4739        æˇ»
 4740    "});
 4741    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4742    cx.assert_editor_state(indoc! {"
 4743        «æ
 4744 4745        1
 4746        æ
 4747        12
 4748        123ˇ»
 4749    "});
 4750
 4751    // Test reverse_lines()
 4752    cx.set_state(indoc! {"
 4753        «5
 4754        4
 4755        3
 4756        2
 4757        1ˇ»
 4758    "});
 4759    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4760    cx.assert_editor_state(indoc! {"
 4761        «1
 4762        2
 4763        3
 4764        4
 4765        5ˇ»
 4766    "});
 4767
 4768    // Skip testing shuffle_line()
 4769
 4770    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4771    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4772
 4773    // Don't manipulate when cursor is on single line, but expand the selection
 4774    cx.set_state(indoc! {"
 4775        ddˇdd
 4776        ccc
 4777        bb
 4778        a
 4779    "});
 4780    cx.update_editor(|e, window, cx| {
 4781        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4782    });
 4783    cx.assert_editor_state(indoc! {"
 4784        «ddddˇ»
 4785        ccc
 4786        bb
 4787        a
 4788    "});
 4789
 4790    // Basic manipulate case
 4791    // Start selection moves to column 0
 4792    // End of selection shrinks to fit shorter line
 4793    cx.set_state(indoc! {"
 4794        dd«d
 4795        ccc
 4796        bb
 4797        aaaaaˇ»
 4798    "});
 4799    cx.update_editor(|e, window, cx| {
 4800        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4801    });
 4802    cx.assert_editor_state(indoc! {"
 4803        «aaaaa
 4804        bb
 4805        ccc
 4806        dddˇ»
 4807    "});
 4808
 4809    // Manipulate case with newlines
 4810    cx.set_state(indoc! {"
 4811        dd«d
 4812        ccc
 4813
 4814        bb
 4815        aaaaa
 4816
 4817        ˇ»
 4818    "});
 4819    cx.update_editor(|e, window, cx| {
 4820        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4821    });
 4822    cx.assert_editor_state(indoc! {"
 4823        «
 4824
 4825        aaaaa
 4826        bb
 4827        ccc
 4828        dddˇ»
 4829
 4830    "});
 4831
 4832    // Adding new line
 4833    cx.set_state(indoc! {"
 4834        aa«a
 4835        bbˇ»b
 4836    "});
 4837    cx.update_editor(|e, window, cx| {
 4838        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4839    });
 4840    cx.assert_editor_state(indoc! {"
 4841        «aaa
 4842        bbb
 4843        added_lineˇ»
 4844    "});
 4845
 4846    // Removing line
 4847    cx.set_state(indoc! {"
 4848        aa«a
 4849        bbbˇ»
 4850    "});
 4851    cx.update_editor(|e, window, cx| {
 4852        e.manipulate_immutable_lines(window, cx, |lines| {
 4853            lines.pop();
 4854        })
 4855    });
 4856    cx.assert_editor_state(indoc! {"
 4857        «aaaˇ»
 4858    "});
 4859
 4860    // Removing all lines
 4861    cx.set_state(indoc! {"
 4862        aa«a
 4863        bbbˇ»
 4864    "});
 4865    cx.update_editor(|e, window, cx| {
 4866        e.manipulate_immutable_lines(window, cx, |lines| {
 4867            lines.drain(..);
 4868        })
 4869    });
 4870    cx.assert_editor_state(indoc! {"
 4871        ˇ
 4872    "});
 4873}
 4874
 4875#[gpui::test]
 4876async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4877    init_test(cx, |_| {});
 4878
 4879    let mut cx = EditorTestContext::new(cx).await;
 4880
 4881    // Consider continuous selection as single selection
 4882    cx.set_state(indoc! {"
 4883        Aaa«aa
 4884        cˇ»c«c
 4885        bb
 4886        aaaˇ»aa
 4887    "});
 4888    cx.update_editor(|e, window, cx| {
 4889        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4890    });
 4891    cx.assert_editor_state(indoc! {"
 4892        «Aaaaa
 4893        ccc
 4894        bb
 4895        aaaaaˇ»
 4896    "});
 4897
 4898    cx.set_state(indoc! {"
 4899        Aaa«aa
 4900        cˇ»c«c
 4901        bb
 4902        aaaˇ»aa
 4903    "});
 4904    cx.update_editor(|e, window, cx| {
 4905        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4906    });
 4907    cx.assert_editor_state(indoc! {"
 4908        «Aaaaa
 4909        ccc
 4910        bbˇ»
 4911    "});
 4912
 4913    // Consider non continuous selection as distinct dedup operations
 4914    cx.set_state(indoc! {"
 4915        «aaaaa
 4916        bb
 4917        aaaaa
 4918        aaaaaˇ»
 4919
 4920        aaa«aaˇ»
 4921    "});
 4922    cx.update_editor(|e, window, cx| {
 4923        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4924    });
 4925    cx.assert_editor_state(indoc! {"
 4926        «aaaaa
 4927        bbˇ»
 4928
 4929        «aaaaaˇ»
 4930    "});
 4931}
 4932
 4933#[gpui::test]
 4934async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4935    init_test(cx, |_| {});
 4936
 4937    let mut cx = EditorTestContext::new(cx).await;
 4938
 4939    cx.set_state(indoc! {"
 4940        «Aaa
 4941        aAa
 4942        Aaaˇ»
 4943    "});
 4944    cx.update_editor(|e, window, cx| {
 4945        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4946    });
 4947    cx.assert_editor_state(indoc! {"
 4948        «Aaa
 4949        aAaˇ»
 4950    "});
 4951
 4952    cx.set_state(indoc! {"
 4953        «Aaa
 4954        aAa
 4955        aaAˇ»
 4956    "});
 4957    cx.update_editor(|e, window, cx| {
 4958        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4959    });
 4960    cx.assert_editor_state(indoc! {"
 4961        «Aaaˇ»
 4962    "});
 4963}
 4964
 4965#[gpui::test]
 4966async fn test_wrap_in_tag_single_selection(cx: &mut TestAppContext) {
 4967    init_test(cx, |_| {});
 4968
 4969    let mut cx = EditorTestContext::new(cx).await;
 4970
 4971    let js_language = Arc::new(Language::new(
 4972        LanguageConfig {
 4973            name: "JavaScript".into(),
 4974            wrap_characters: Some(language::WrapCharactersConfig {
 4975                start_prefix: "<".into(),
 4976                start_suffix: ">".into(),
 4977                end_prefix: "</".into(),
 4978                end_suffix: ">".into(),
 4979            }),
 4980            ..LanguageConfig::default()
 4981        },
 4982        None,
 4983    ));
 4984
 4985    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 4986
 4987    cx.set_state(indoc! {"
 4988        «testˇ»
 4989    "});
 4990    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4991    cx.assert_editor_state(indoc! {"
 4992        <«ˇ»>test</«ˇ»>
 4993    "});
 4994
 4995    cx.set_state(indoc! {"
 4996        «test
 4997         testˇ»
 4998    "});
 4999    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5000    cx.assert_editor_state(indoc! {"
 5001        <«ˇ»>test
 5002         test</«ˇ»>
 5003    "});
 5004
 5005    cx.set_state(indoc! {"
 5006        teˇst
 5007    "});
 5008    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5009    cx.assert_editor_state(indoc! {"
 5010        te<«ˇ»></«ˇ»>st
 5011    "});
 5012}
 5013
 5014#[gpui::test]
 5015async fn test_wrap_in_tag_multi_selection(cx: &mut TestAppContext) {
 5016    init_test(cx, |_| {});
 5017
 5018    let mut cx = EditorTestContext::new(cx).await;
 5019
 5020    let js_language = Arc::new(Language::new(
 5021        LanguageConfig {
 5022            name: "JavaScript".into(),
 5023            wrap_characters: Some(language::WrapCharactersConfig {
 5024                start_prefix: "<".into(),
 5025                start_suffix: ">".into(),
 5026                end_prefix: "</".into(),
 5027                end_suffix: ">".into(),
 5028            }),
 5029            ..LanguageConfig::default()
 5030        },
 5031        None,
 5032    ));
 5033
 5034    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 5035
 5036    cx.set_state(indoc! {"
 5037        «testˇ»
 5038        «testˇ» «testˇ»
 5039        «testˇ»
 5040    "});
 5041    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5042    cx.assert_editor_state(indoc! {"
 5043        <«ˇ»>test</«ˇ»>
 5044        <«ˇ»>test</«ˇ»> <«ˇ»>test</«ˇ»>
 5045        <«ˇ»>test</«ˇ»>
 5046    "});
 5047
 5048    cx.set_state(indoc! {"
 5049        «test
 5050         testˇ»
 5051        «test
 5052         testˇ»
 5053    "});
 5054    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5055    cx.assert_editor_state(indoc! {"
 5056        <«ˇ»>test
 5057         test</«ˇ»>
 5058        <«ˇ»>test
 5059         test</«ˇ»>
 5060    "});
 5061}
 5062
 5063#[gpui::test]
 5064async fn test_wrap_in_tag_does_nothing_in_unsupported_languages(cx: &mut TestAppContext) {
 5065    init_test(cx, |_| {});
 5066
 5067    let mut cx = EditorTestContext::new(cx).await;
 5068
 5069    let plaintext_language = Arc::new(Language::new(
 5070        LanguageConfig {
 5071            name: "Plain Text".into(),
 5072            ..LanguageConfig::default()
 5073        },
 5074        None,
 5075    ));
 5076
 5077    cx.update_buffer(|buffer, cx| buffer.set_language(Some(plaintext_language), cx));
 5078
 5079    cx.set_state(indoc! {"
 5080        «testˇ»
 5081    "});
 5082    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5083    cx.assert_editor_state(indoc! {"
 5084      «testˇ»
 5085    "});
 5086}
 5087
 5088#[gpui::test]
 5089async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 5090    init_test(cx, |_| {});
 5091
 5092    let mut cx = EditorTestContext::new(cx).await;
 5093
 5094    // Manipulate with multiple selections on a single line
 5095    cx.set_state(indoc! {"
 5096        dd«dd
 5097        cˇ»c«c
 5098        bb
 5099        aaaˇ»aa
 5100    "});
 5101    cx.update_editor(|e, window, cx| {
 5102        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5103    });
 5104    cx.assert_editor_state(indoc! {"
 5105        «aaaaa
 5106        bb
 5107        ccc
 5108        ddddˇ»
 5109    "});
 5110
 5111    // Manipulate with multiple disjoin selections
 5112    cx.set_state(indoc! {"
 5113 5114        4
 5115        3
 5116        2
 5117        1ˇ»
 5118
 5119        dd«dd
 5120        ccc
 5121        bb
 5122        aaaˇ»aa
 5123    "});
 5124    cx.update_editor(|e, window, cx| {
 5125        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5126    });
 5127    cx.assert_editor_state(indoc! {"
 5128        «1
 5129        2
 5130        3
 5131        4
 5132        5ˇ»
 5133
 5134        «aaaaa
 5135        bb
 5136        ccc
 5137        ddddˇ»
 5138    "});
 5139
 5140    // Adding lines on each selection
 5141    cx.set_state(indoc! {"
 5142 5143        1ˇ»
 5144
 5145        bb«bb
 5146        aaaˇ»aa
 5147    "});
 5148    cx.update_editor(|e, window, cx| {
 5149        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 5150    });
 5151    cx.assert_editor_state(indoc! {"
 5152        «2
 5153        1
 5154        added lineˇ»
 5155
 5156        «bbbb
 5157        aaaaa
 5158        added lineˇ»
 5159    "});
 5160
 5161    // Removing lines on each selection
 5162    cx.set_state(indoc! {"
 5163 5164        1ˇ»
 5165
 5166        bb«bb
 5167        aaaˇ»aa
 5168    "});
 5169    cx.update_editor(|e, window, cx| {
 5170        e.manipulate_immutable_lines(window, cx, |lines| {
 5171            lines.pop();
 5172        })
 5173    });
 5174    cx.assert_editor_state(indoc! {"
 5175        «2ˇ»
 5176
 5177        «bbbbˇ»
 5178    "});
 5179}
 5180
 5181#[gpui::test]
 5182async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 5183    init_test(cx, |settings| {
 5184        settings.defaults.tab_size = NonZeroU32::new(3)
 5185    });
 5186
 5187    let mut cx = EditorTestContext::new(cx).await;
 5188
 5189    // MULTI SELECTION
 5190    // Ln.1 "«" tests empty lines
 5191    // Ln.9 tests just leading whitespace
 5192    cx.set_state(indoc! {"
 5193        «
 5194        abc                 // No indentationˇ»
 5195        «\tabc              // 1 tabˇ»
 5196        \t\tabc «      ˇ»   // 2 tabs
 5197        \t ab«c             // Tab followed by space
 5198         \tabc              // Space followed by tab (3 spaces should be the result)
 5199        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5200           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 5201        \t
 5202        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5203    "});
 5204    cx.update_editor(|e, window, cx| {
 5205        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5206    });
 5207    cx.assert_editor_state(
 5208        indoc! {"
 5209            «
 5210            abc                 // No indentation
 5211               abc              // 1 tab
 5212                  abc          // 2 tabs
 5213                abc             // Tab followed by space
 5214               abc              // Space followed by tab (3 spaces should be the result)
 5215                           abc   // Mixed indentation (tab conversion depends on the column)
 5216               abc         // Already space indented
 5217               ·
 5218               abc\tdef          // Only the leading tab is manipulatedˇ»
 5219        "}
 5220        .replace("·", "")
 5221        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5222    );
 5223
 5224    // Test on just a few lines, the others should remain unchanged
 5225    // Only lines (3, 5, 10, 11) should change
 5226    cx.set_state(
 5227        indoc! {"
 5228            ·
 5229            abc                 // No indentation
 5230            \tabcˇ               // 1 tab
 5231            \t\tabc             // 2 tabs
 5232            \t abcˇ              // Tab followed by space
 5233             \tabc              // Space followed by tab (3 spaces should be the result)
 5234            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5235               abc              // Already space indented
 5236            «\t
 5237            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5238        "}
 5239        .replace("·", "")
 5240        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5241    );
 5242    cx.update_editor(|e, window, cx| {
 5243        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5244    });
 5245    cx.assert_editor_state(
 5246        indoc! {"
 5247            ·
 5248            abc                 // No indentation
 5249            «   abc               // 1 tabˇ»
 5250            \t\tabc             // 2 tabs
 5251            «    abc              // Tab followed by spaceˇ»
 5252             \tabc              // Space followed by tab (3 spaces should be the result)
 5253            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5254               abc              // Already space indented
 5255            «   ·
 5256               abc\tdef          // Only the leading tab is manipulatedˇ»
 5257        "}
 5258        .replace("·", "")
 5259        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5260    );
 5261
 5262    // SINGLE SELECTION
 5263    // Ln.1 "«" tests empty lines
 5264    // Ln.9 tests just leading whitespace
 5265    cx.set_state(indoc! {"
 5266        «
 5267        abc                 // No indentation
 5268        \tabc               // 1 tab
 5269        \t\tabc             // 2 tabs
 5270        \t abc              // Tab followed by space
 5271         \tabc              // Space followed by tab (3 spaces should be the result)
 5272        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5273           abc              // Already space indented
 5274        \t
 5275        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5276    "});
 5277    cx.update_editor(|e, window, cx| {
 5278        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5279    });
 5280    cx.assert_editor_state(
 5281        indoc! {"
 5282            «
 5283            abc                 // No indentation
 5284               abc               // 1 tab
 5285                  abc             // 2 tabs
 5286                abc              // Tab followed by space
 5287               abc              // Space followed by tab (3 spaces should be the result)
 5288                           abc   // Mixed indentation (tab conversion depends on the column)
 5289               abc              // Already space indented
 5290               ·
 5291               abc\tdef          // Only the leading tab is manipulatedˇ»
 5292        "}
 5293        .replace("·", "")
 5294        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5295    );
 5296}
 5297
 5298#[gpui::test]
 5299async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 5300    init_test(cx, |settings| {
 5301        settings.defaults.tab_size = NonZeroU32::new(3)
 5302    });
 5303
 5304    let mut cx = EditorTestContext::new(cx).await;
 5305
 5306    // MULTI SELECTION
 5307    // Ln.1 "«" tests empty lines
 5308    // Ln.11 tests just leading whitespace
 5309    cx.set_state(indoc! {"
 5310        «
 5311        abˇ»ˇc                 // No indentation
 5312         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 5313          abc  «             // 2 spaces (< 3 so dont convert)
 5314           abc              // 3 spaces (convert)
 5315             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 5316        «\tˇ»\t«\tˇ»abc           // Already tab indented
 5317        «\t abc              // Tab followed by space
 5318         \tabc              // Space followed by tab (should be consumed due to tab)
 5319        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5320           \tˇ»  «\t
 5321           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 5322    "});
 5323    cx.update_editor(|e, window, cx| {
 5324        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5325    });
 5326    cx.assert_editor_state(indoc! {"
 5327        «
 5328        abc                 // No indentation
 5329         abc                // 1 space (< 3 so dont convert)
 5330          abc               // 2 spaces (< 3 so dont convert)
 5331        \tabc              // 3 spaces (convert)
 5332        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5333        \t\t\tabc           // Already tab indented
 5334        \t abc              // Tab followed by space
 5335        \tabc              // Space followed by tab (should be consumed due to tab)
 5336        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5337        \t\t\t
 5338        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5339    "});
 5340
 5341    // Test on just a few lines, the other should remain unchanged
 5342    // Only lines (4, 8, 11, 12) should change
 5343    cx.set_state(
 5344        indoc! {"
 5345            ·
 5346            abc                 // No indentation
 5347             abc                // 1 space (< 3 so dont convert)
 5348              abc               // 2 spaces (< 3 so dont convert)
 5349            «   abc              // 3 spaces (convert)ˇ»
 5350                 abc            // 5 spaces (1 tab + 2 spaces)
 5351            \t\t\tabc           // Already tab indented
 5352            \t abc              // Tab followed by space
 5353             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 5354               \t\t  \tabc      // Mixed indentation
 5355            \t \t  \t   \tabc   // Mixed indentation
 5356               \t  \tˇ
 5357            «   abc   \t         // Only the leading spaces should be convertedˇ»
 5358        "}
 5359        .replace("·", "")
 5360        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5361    );
 5362    cx.update_editor(|e, window, cx| {
 5363        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5364    });
 5365    cx.assert_editor_state(
 5366        indoc! {"
 5367            ·
 5368            abc                 // No indentation
 5369             abc                // 1 space (< 3 so dont convert)
 5370              abc               // 2 spaces (< 3 so dont convert)
 5371            «\tabc              // 3 spaces (convert)ˇ»
 5372                 abc            // 5 spaces (1 tab + 2 spaces)
 5373            \t\t\tabc           // Already tab indented
 5374            \t abc              // Tab followed by space
 5375            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 5376               \t\t  \tabc      // Mixed indentation
 5377            \t \t  \t   \tabc   // Mixed indentation
 5378            «\t\t\t
 5379            \tabc   \t         // Only the leading spaces should be convertedˇ»
 5380        "}
 5381        .replace("·", "")
 5382        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5383    );
 5384
 5385    // SINGLE SELECTION
 5386    // Ln.1 "«" tests empty lines
 5387    // Ln.11 tests just leading whitespace
 5388    cx.set_state(indoc! {"
 5389        «
 5390        abc                 // No indentation
 5391         abc                // 1 space (< 3 so dont convert)
 5392          abc               // 2 spaces (< 3 so dont convert)
 5393           abc              // 3 spaces (convert)
 5394             abc            // 5 spaces (1 tab + 2 spaces)
 5395        \t\t\tabc           // Already tab indented
 5396        \t abc              // Tab followed by space
 5397         \tabc              // Space followed by tab (should be consumed due to tab)
 5398        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5399           \t  \t
 5400           abc   \t         // Only the leading spaces should be convertedˇ»
 5401    "});
 5402    cx.update_editor(|e, window, cx| {
 5403        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5404    });
 5405    cx.assert_editor_state(indoc! {"
 5406        «
 5407        abc                 // No indentation
 5408         abc                // 1 space (< 3 so dont convert)
 5409          abc               // 2 spaces (< 3 so dont convert)
 5410        \tabc              // 3 spaces (convert)
 5411        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5412        \t\t\tabc           // Already tab indented
 5413        \t abc              // Tab followed by space
 5414        \tabc              // Space followed by tab (should be consumed due to tab)
 5415        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5416        \t\t\t
 5417        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5418    "});
 5419}
 5420
 5421#[gpui::test]
 5422async fn test_toggle_case(cx: &mut TestAppContext) {
 5423    init_test(cx, |_| {});
 5424
 5425    let mut cx = EditorTestContext::new(cx).await;
 5426
 5427    // If all lower case -> upper case
 5428    cx.set_state(indoc! {"
 5429        «hello worldˇ»
 5430    "});
 5431    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5432    cx.assert_editor_state(indoc! {"
 5433        «HELLO WORLDˇ»
 5434    "});
 5435
 5436    // If all upper case -> lower case
 5437    cx.set_state(indoc! {"
 5438        «HELLO WORLDˇ»
 5439    "});
 5440    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5441    cx.assert_editor_state(indoc! {"
 5442        «hello worldˇ»
 5443    "});
 5444
 5445    // If any upper case characters are identified -> lower case
 5446    // This matches JetBrains IDEs
 5447    cx.set_state(indoc! {"
 5448        «hEllo worldˇ»
 5449    "});
 5450    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5451    cx.assert_editor_state(indoc! {"
 5452        «hello worldˇ»
 5453    "});
 5454}
 5455
 5456#[gpui::test]
 5457async fn test_convert_to_sentence_case(cx: &mut TestAppContext) {
 5458    init_test(cx, |_| {});
 5459
 5460    let mut cx = EditorTestContext::new(cx).await;
 5461
 5462    cx.set_state(indoc! {"
 5463        «implement-windows-supportˇ»
 5464    "});
 5465    cx.update_editor(|e, window, cx| {
 5466        e.convert_to_sentence_case(&ConvertToSentenceCase, window, cx)
 5467    });
 5468    cx.assert_editor_state(indoc! {"
 5469        «Implement windows supportˇ»
 5470    "});
 5471}
 5472
 5473#[gpui::test]
 5474async fn test_manipulate_text(cx: &mut TestAppContext) {
 5475    init_test(cx, |_| {});
 5476
 5477    let mut cx = EditorTestContext::new(cx).await;
 5478
 5479    // Test convert_to_upper_case()
 5480    cx.set_state(indoc! {"
 5481        «hello worldˇ»
 5482    "});
 5483    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5484    cx.assert_editor_state(indoc! {"
 5485        «HELLO WORLDˇ»
 5486    "});
 5487
 5488    // Test convert_to_lower_case()
 5489    cx.set_state(indoc! {"
 5490        «HELLO WORLDˇ»
 5491    "});
 5492    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 5493    cx.assert_editor_state(indoc! {"
 5494        «hello worldˇ»
 5495    "});
 5496
 5497    // Test multiple line, single selection case
 5498    cx.set_state(indoc! {"
 5499        «The quick brown
 5500        fox jumps over
 5501        the lazy dogˇ»
 5502    "});
 5503    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 5504    cx.assert_editor_state(indoc! {"
 5505        «The Quick Brown
 5506        Fox Jumps Over
 5507        The Lazy Dogˇ»
 5508    "});
 5509
 5510    // Test multiple line, single selection case
 5511    cx.set_state(indoc! {"
 5512        «The quick brown
 5513        fox jumps over
 5514        the lazy dogˇ»
 5515    "});
 5516    cx.update_editor(|e, window, cx| {
 5517        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 5518    });
 5519    cx.assert_editor_state(indoc! {"
 5520        «TheQuickBrown
 5521        FoxJumpsOver
 5522        TheLazyDogˇ»
 5523    "});
 5524
 5525    // From here on out, test more complex cases of manipulate_text()
 5526
 5527    // Test no selection case - should affect words cursors are in
 5528    // Cursor at beginning, middle, and end of word
 5529    cx.set_state(indoc! {"
 5530        ˇhello big beauˇtiful worldˇ
 5531    "});
 5532    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5533    cx.assert_editor_state(indoc! {"
 5534        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 5535    "});
 5536
 5537    // Test multiple selections on a single line and across multiple lines
 5538    cx.set_state(indoc! {"
 5539        «Theˇ» quick «brown
 5540        foxˇ» jumps «overˇ»
 5541        the «lazyˇ» dog
 5542    "});
 5543    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5544    cx.assert_editor_state(indoc! {"
 5545        «THEˇ» quick «BROWN
 5546        FOXˇ» jumps «OVERˇ»
 5547        the «LAZYˇ» dog
 5548    "});
 5549
 5550    // Test case where text length grows
 5551    cx.set_state(indoc! {"
 5552        «tschüߡ»
 5553    "});
 5554    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5555    cx.assert_editor_state(indoc! {"
 5556        «TSCHÜSSˇ»
 5557    "});
 5558
 5559    // Test to make sure we don't crash when text shrinks
 5560    cx.set_state(indoc! {"
 5561        aaa_bbbˇ
 5562    "});
 5563    cx.update_editor(|e, window, cx| {
 5564        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5565    });
 5566    cx.assert_editor_state(indoc! {"
 5567        «aaaBbbˇ»
 5568    "});
 5569
 5570    // Test to make sure we all aware of the fact that each word can grow and shrink
 5571    // Final selections should be aware of this fact
 5572    cx.set_state(indoc! {"
 5573        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 5574    "});
 5575    cx.update_editor(|e, window, cx| {
 5576        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5577    });
 5578    cx.assert_editor_state(indoc! {"
 5579        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 5580    "});
 5581
 5582    cx.set_state(indoc! {"
 5583        «hElLo, WoRld!ˇ»
 5584    "});
 5585    cx.update_editor(|e, window, cx| {
 5586        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 5587    });
 5588    cx.assert_editor_state(indoc! {"
 5589        «HeLlO, wOrLD!ˇ»
 5590    "});
 5591
 5592    // Test selections with `line_mode() = true`.
 5593    cx.update_editor(|editor, _window, _cx| editor.selections.set_line_mode(true));
 5594    cx.set_state(indoc! {"
 5595        «The quick brown
 5596        fox jumps over
 5597        tˇ»he lazy dog
 5598    "});
 5599    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5600    cx.assert_editor_state(indoc! {"
 5601        «THE QUICK BROWN
 5602        FOX JUMPS OVER
 5603        THE LAZY DOGˇ»
 5604    "});
 5605}
 5606
 5607#[gpui::test]
 5608fn test_duplicate_line(cx: &mut TestAppContext) {
 5609    init_test(cx, |_| {});
 5610
 5611    let editor = cx.add_window(|window, cx| {
 5612        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5613        build_editor(buffer, window, cx)
 5614    });
 5615    _ = editor.update(cx, |editor, window, cx| {
 5616        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5617            s.select_display_ranges([
 5618                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5619                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5620                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5621                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5622            ])
 5623        });
 5624        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5625        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5626        assert_eq!(
 5627            display_ranges(editor, cx),
 5628            vec![
 5629                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 5630                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 5631                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5632                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5633            ]
 5634        );
 5635    });
 5636
 5637    let editor = cx.add_window(|window, cx| {
 5638        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5639        build_editor(buffer, window, cx)
 5640    });
 5641    _ = editor.update(cx, |editor, window, cx| {
 5642        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5643            s.select_display_ranges([
 5644                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5645                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5646            ])
 5647        });
 5648        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5649        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5650        assert_eq!(
 5651            display_ranges(editor, cx),
 5652            vec![
 5653                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 5654                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 5655            ]
 5656        );
 5657    });
 5658
 5659    // With `duplicate_line_up` the selections move to the duplicated lines,
 5660    // which are inserted above the original lines
 5661    let editor = cx.add_window(|window, cx| {
 5662        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5663        build_editor(buffer, window, cx)
 5664    });
 5665    _ = editor.update(cx, |editor, window, cx| {
 5666        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5667            s.select_display_ranges([
 5668                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5669                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5670                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5671                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5672            ])
 5673        });
 5674        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5675        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5676        assert_eq!(
 5677            display_ranges(editor, cx),
 5678            vec![
 5679                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5680                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5681                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 5682                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0),
 5683            ]
 5684        );
 5685    });
 5686
 5687    let editor = cx.add_window(|window, cx| {
 5688        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5689        build_editor(buffer, window, cx)
 5690    });
 5691    _ = editor.update(cx, |editor, window, cx| {
 5692        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5693            s.select_display_ranges([
 5694                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5695                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5696            ])
 5697        });
 5698        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5699        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5700        assert_eq!(
 5701            display_ranges(editor, cx),
 5702            vec![
 5703                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5704                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5705            ]
 5706        );
 5707    });
 5708
 5709    let editor = cx.add_window(|window, cx| {
 5710        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5711        build_editor(buffer, window, cx)
 5712    });
 5713    _ = editor.update(cx, |editor, window, cx| {
 5714        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5715            s.select_display_ranges([
 5716                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5717                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5718            ])
 5719        });
 5720        editor.duplicate_selection(&DuplicateSelection, window, cx);
 5721        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 5722        assert_eq!(
 5723            display_ranges(editor, cx),
 5724            vec![
 5725                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5726                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 5727            ]
 5728        );
 5729    });
 5730}
 5731
 5732#[gpui::test]
 5733fn test_move_line_up_down(cx: &mut TestAppContext) {
 5734    init_test(cx, |_| {});
 5735
 5736    let editor = cx.add_window(|window, cx| {
 5737        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5738        build_editor(buffer, window, cx)
 5739    });
 5740    _ = editor.update(cx, |editor, window, cx| {
 5741        editor.fold_creases(
 5742            vec![
 5743                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5744                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5745                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5746            ],
 5747            true,
 5748            window,
 5749            cx,
 5750        );
 5751        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5752            s.select_display_ranges([
 5753                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5754                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5755                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5756                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 5757            ])
 5758        });
 5759        assert_eq!(
 5760            editor.display_text(cx),
 5761            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 5762        );
 5763
 5764        editor.move_line_up(&MoveLineUp, window, cx);
 5765        assert_eq!(
 5766            editor.display_text(cx),
 5767            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 5768        );
 5769        assert_eq!(
 5770            display_ranges(editor, cx),
 5771            vec![
 5772                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5773                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5774                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5775                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5776            ]
 5777        );
 5778    });
 5779
 5780    _ = editor.update(cx, |editor, window, cx| {
 5781        editor.move_line_down(&MoveLineDown, window, cx);
 5782        assert_eq!(
 5783            editor.display_text(cx),
 5784            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 5785        );
 5786        assert_eq!(
 5787            display_ranges(editor, cx),
 5788            vec![
 5789                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5790                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5791                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5792                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5793            ]
 5794        );
 5795    });
 5796
 5797    _ = editor.update(cx, |editor, window, cx| {
 5798        editor.move_line_down(&MoveLineDown, window, cx);
 5799        assert_eq!(
 5800            editor.display_text(cx),
 5801            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 5802        );
 5803        assert_eq!(
 5804            display_ranges(editor, cx),
 5805            vec![
 5806                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5807                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5808                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5809                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5810            ]
 5811        );
 5812    });
 5813
 5814    _ = editor.update(cx, |editor, window, cx| {
 5815        editor.move_line_up(&MoveLineUp, window, cx);
 5816        assert_eq!(
 5817            editor.display_text(cx),
 5818            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 5819        );
 5820        assert_eq!(
 5821            display_ranges(editor, cx),
 5822            vec![
 5823                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5824                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5825                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5826                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5827            ]
 5828        );
 5829    });
 5830}
 5831
 5832#[gpui::test]
 5833fn test_move_line_up_selection_at_end_of_fold(cx: &mut TestAppContext) {
 5834    init_test(cx, |_| {});
 5835    let editor = cx.add_window(|window, cx| {
 5836        let buffer = MultiBuffer::build_simple("\n\n\n\n\n\naaaa\nbbbb\ncccc", cx);
 5837        build_editor(buffer, window, cx)
 5838    });
 5839    _ = editor.update(cx, |editor, window, cx| {
 5840        editor.fold_creases(
 5841            vec![Crease::simple(
 5842                Point::new(6, 4)..Point::new(7, 4),
 5843                FoldPlaceholder::test(),
 5844            )],
 5845            true,
 5846            window,
 5847            cx,
 5848        );
 5849        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5850            s.select_ranges([Point::new(7, 4)..Point::new(7, 4)])
 5851        });
 5852        assert_eq!(editor.display_text(cx), "\n\n\n\n\n\naaaa⋯\ncccc");
 5853        editor.move_line_up(&MoveLineUp, window, cx);
 5854        let buffer_text = editor.buffer.read(cx).snapshot(cx).text();
 5855        assert_eq!(buffer_text, "\n\n\n\n\naaaa\nbbbb\n\ncccc");
 5856    });
 5857}
 5858
 5859#[gpui::test]
 5860fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 5861    init_test(cx, |_| {});
 5862
 5863    let editor = cx.add_window(|window, cx| {
 5864        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5865        build_editor(buffer, window, cx)
 5866    });
 5867    _ = editor.update(cx, |editor, window, cx| {
 5868        let snapshot = editor.buffer.read(cx).snapshot(cx);
 5869        editor.insert_blocks(
 5870            [BlockProperties {
 5871                style: BlockStyle::Fixed,
 5872                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 5873                height: Some(1),
 5874                render: Arc::new(|_| div().into_any()),
 5875                priority: 0,
 5876            }],
 5877            Some(Autoscroll::fit()),
 5878            cx,
 5879        );
 5880        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5881            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 5882        });
 5883        editor.move_line_down(&MoveLineDown, window, cx);
 5884    });
 5885}
 5886
 5887#[gpui::test]
 5888async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 5889    init_test(cx, |_| {});
 5890
 5891    let mut cx = EditorTestContext::new(cx).await;
 5892    cx.set_state(
 5893        &"
 5894            ˇzero
 5895            one
 5896            two
 5897            three
 5898            four
 5899            five
 5900        "
 5901        .unindent(),
 5902    );
 5903
 5904    // Create a four-line block that replaces three lines of text.
 5905    cx.update_editor(|editor, window, cx| {
 5906        let snapshot = editor.snapshot(window, cx);
 5907        let snapshot = &snapshot.buffer_snapshot();
 5908        let placement = BlockPlacement::Replace(
 5909            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 5910        );
 5911        editor.insert_blocks(
 5912            [BlockProperties {
 5913                placement,
 5914                height: Some(4),
 5915                style: BlockStyle::Sticky,
 5916                render: Arc::new(|_| gpui::div().into_any_element()),
 5917                priority: 0,
 5918            }],
 5919            None,
 5920            cx,
 5921        );
 5922    });
 5923
 5924    // Move down so that the cursor touches the block.
 5925    cx.update_editor(|editor, window, cx| {
 5926        editor.move_down(&Default::default(), window, cx);
 5927    });
 5928    cx.assert_editor_state(
 5929        &"
 5930            zero
 5931            «one
 5932            two
 5933            threeˇ»
 5934            four
 5935            five
 5936        "
 5937        .unindent(),
 5938    );
 5939
 5940    // Move down past the block.
 5941    cx.update_editor(|editor, window, cx| {
 5942        editor.move_down(&Default::default(), window, cx);
 5943    });
 5944    cx.assert_editor_state(
 5945        &"
 5946            zero
 5947            one
 5948            two
 5949            three
 5950            ˇfour
 5951            five
 5952        "
 5953        .unindent(),
 5954    );
 5955}
 5956
 5957#[gpui::test]
 5958fn test_transpose(cx: &mut TestAppContext) {
 5959    init_test(cx, |_| {});
 5960
 5961    _ = cx.add_window(|window, cx| {
 5962        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 5963        editor.set_style(EditorStyle::default(), window, cx);
 5964        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5965            s.select_ranges([1..1])
 5966        });
 5967        editor.transpose(&Default::default(), window, cx);
 5968        assert_eq!(editor.text(cx), "bac");
 5969        assert_eq!(
 5970            editor.selections.ranges(&editor.display_snapshot(cx)),
 5971            [2..2]
 5972        );
 5973
 5974        editor.transpose(&Default::default(), window, cx);
 5975        assert_eq!(editor.text(cx), "bca");
 5976        assert_eq!(
 5977            editor.selections.ranges(&editor.display_snapshot(cx)),
 5978            [3..3]
 5979        );
 5980
 5981        editor.transpose(&Default::default(), window, cx);
 5982        assert_eq!(editor.text(cx), "bac");
 5983        assert_eq!(
 5984            editor.selections.ranges(&editor.display_snapshot(cx)),
 5985            [3..3]
 5986        );
 5987
 5988        editor
 5989    });
 5990
 5991    _ = cx.add_window(|window, cx| {
 5992        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5993        editor.set_style(EditorStyle::default(), window, cx);
 5994        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5995            s.select_ranges([3..3])
 5996        });
 5997        editor.transpose(&Default::default(), window, cx);
 5998        assert_eq!(editor.text(cx), "acb\nde");
 5999        assert_eq!(
 6000            editor.selections.ranges(&editor.display_snapshot(cx)),
 6001            [3..3]
 6002        );
 6003
 6004        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6005            s.select_ranges([4..4])
 6006        });
 6007        editor.transpose(&Default::default(), window, cx);
 6008        assert_eq!(editor.text(cx), "acbd\ne");
 6009        assert_eq!(
 6010            editor.selections.ranges(&editor.display_snapshot(cx)),
 6011            [5..5]
 6012        );
 6013
 6014        editor.transpose(&Default::default(), window, cx);
 6015        assert_eq!(editor.text(cx), "acbde\n");
 6016        assert_eq!(
 6017            editor.selections.ranges(&editor.display_snapshot(cx)),
 6018            [6..6]
 6019        );
 6020
 6021        editor.transpose(&Default::default(), window, cx);
 6022        assert_eq!(editor.text(cx), "acbd\ne");
 6023        assert_eq!(
 6024            editor.selections.ranges(&editor.display_snapshot(cx)),
 6025            [6..6]
 6026        );
 6027
 6028        editor
 6029    });
 6030
 6031    _ = cx.add_window(|window, cx| {
 6032        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 6033        editor.set_style(EditorStyle::default(), window, cx);
 6034        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6035            s.select_ranges([1..1, 2..2, 4..4])
 6036        });
 6037        editor.transpose(&Default::default(), window, cx);
 6038        assert_eq!(editor.text(cx), "bacd\ne");
 6039        assert_eq!(
 6040            editor.selections.ranges(&editor.display_snapshot(cx)),
 6041            [2..2, 3..3, 5..5]
 6042        );
 6043
 6044        editor.transpose(&Default::default(), window, cx);
 6045        assert_eq!(editor.text(cx), "bcade\n");
 6046        assert_eq!(
 6047            editor.selections.ranges(&editor.display_snapshot(cx)),
 6048            [3..3, 4..4, 6..6]
 6049        );
 6050
 6051        editor.transpose(&Default::default(), window, cx);
 6052        assert_eq!(editor.text(cx), "bcda\ne");
 6053        assert_eq!(
 6054            editor.selections.ranges(&editor.display_snapshot(cx)),
 6055            [4..4, 6..6]
 6056        );
 6057
 6058        editor.transpose(&Default::default(), window, cx);
 6059        assert_eq!(editor.text(cx), "bcade\n");
 6060        assert_eq!(
 6061            editor.selections.ranges(&editor.display_snapshot(cx)),
 6062            [4..4, 6..6]
 6063        );
 6064
 6065        editor.transpose(&Default::default(), window, cx);
 6066        assert_eq!(editor.text(cx), "bcaed\n");
 6067        assert_eq!(
 6068            editor.selections.ranges(&editor.display_snapshot(cx)),
 6069            [5..5, 6..6]
 6070        );
 6071
 6072        editor
 6073    });
 6074
 6075    _ = cx.add_window(|window, cx| {
 6076        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 6077        editor.set_style(EditorStyle::default(), window, cx);
 6078        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6079            s.select_ranges([4..4])
 6080        });
 6081        editor.transpose(&Default::default(), window, cx);
 6082        assert_eq!(editor.text(cx), "🏀🍐✋");
 6083        assert_eq!(
 6084            editor.selections.ranges(&editor.display_snapshot(cx)),
 6085            [8..8]
 6086        );
 6087
 6088        editor.transpose(&Default::default(), window, cx);
 6089        assert_eq!(editor.text(cx), "🏀✋🍐");
 6090        assert_eq!(
 6091            editor.selections.ranges(&editor.display_snapshot(cx)),
 6092            [11..11]
 6093        );
 6094
 6095        editor.transpose(&Default::default(), window, cx);
 6096        assert_eq!(editor.text(cx), "🏀🍐✋");
 6097        assert_eq!(
 6098            editor.selections.ranges(&editor.display_snapshot(cx)),
 6099            [11..11]
 6100        );
 6101
 6102        editor
 6103    });
 6104}
 6105
 6106#[gpui::test]
 6107async fn test_rewrap(cx: &mut TestAppContext) {
 6108    init_test(cx, |settings| {
 6109        settings.languages.0.extend([
 6110            (
 6111                "Markdown".into(),
 6112                LanguageSettingsContent {
 6113                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 6114                    preferred_line_length: Some(40),
 6115                    ..Default::default()
 6116                },
 6117            ),
 6118            (
 6119                "Plain Text".into(),
 6120                LanguageSettingsContent {
 6121                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 6122                    preferred_line_length: Some(40),
 6123                    ..Default::default()
 6124                },
 6125            ),
 6126            (
 6127                "C++".into(),
 6128                LanguageSettingsContent {
 6129                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6130                    preferred_line_length: Some(40),
 6131                    ..Default::default()
 6132                },
 6133            ),
 6134            (
 6135                "Python".into(),
 6136                LanguageSettingsContent {
 6137                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6138                    preferred_line_length: Some(40),
 6139                    ..Default::default()
 6140                },
 6141            ),
 6142            (
 6143                "Rust".into(),
 6144                LanguageSettingsContent {
 6145                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6146                    preferred_line_length: Some(40),
 6147                    ..Default::default()
 6148                },
 6149            ),
 6150        ])
 6151    });
 6152
 6153    let mut cx = EditorTestContext::new(cx).await;
 6154
 6155    let cpp_language = Arc::new(Language::new(
 6156        LanguageConfig {
 6157            name: "C++".into(),
 6158            line_comments: vec!["// ".into()],
 6159            ..LanguageConfig::default()
 6160        },
 6161        None,
 6162    ));
 6163    let python_language = Arc::new(Language::new(
 6164        LanguageConfig {
 6165            name: "Python".into(),
 6166            line_comments: vec!["# ".into()],
 6167            ..LanguageConfig::default()
 6168        },
 6169        None,
 6170    ));
 6171    let markdown_language = Arc::new(Language::new(
 6172        LanguageConfig {
 6173            name: "Markdown".into(),
 6174            rewrap_prefixes: vec![
 6175                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 6176                regex::Regex::new("[-*+]\\s+").unwrap(),
 6177            ],
 6178            ..LanguageConfig::default()
 6179        },
 6180        None,
 6181    ));
 6182    let rust_language = Arc::new(
 6183        Language::new(
 6184            LanguageConfig {
 6185                name: "Rust".into(),
 6186                line_comments: vec!["// ".into(), "/// ".into()],
 6187                ..LanguageConfig::default()
 6188            },
 6189            Some(tree_sitter_rust::LANGUAGE.into()),
 6190        )
 6191        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 6192        .unwrap(),
 6193    );
 6194
 6195    let plaintext_language = Arc::new(Language::new(
 6196        LanguageConfig {
 6197            name: "Plain Text".into(),
 6198            ..LanguageConfig::default()
 6199        },
 6200        None,
 6201    ));
 6202
 6203    // Test basic rewrapping of a long line with a cursor
 6204    assert_rewrap(
 6205        indoc! {"
 6206            // ˇThis is a long comment that needs to be wrapped.
 6207        "},
 6208        indoc! {"
 6209            // ˇThis is a long comment that needs to
 6210            // be wrapped.
 6211        "},
 6212        cpp_language.clone(),
 6213        &mut cx,
 6214    );
 6215
 6216    // Test rewrapping a full selection
 6217    assert_rewrap(
 6218        indoc! {"
 6219            «// This selected long comment needs to be wrapped.ˇ»"
 6220        },
 6221        indoc! {"
 6222            «// This selected long comment needs to
 6223            // be wrapped.ˇ»"
 6224        },
 6225        cpp_language.clone(),
 6226        &mut cx,
 6227    );
 6228
 6229    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 6230    assert_rewrap(
 6231        indoc! {"
 6232            // ˇThis is the first line.
 6233            // Thisˇ is the second line.
 6234            // This is the thirdˇ line, all part of one paragraph.
 6235         "},
 6236        indoc! {"
 6237            // ˇThis is the first line. Thisˇ is the
 6238            // second line. This is the thirdˇ line,
 6239            // all part of one paragraph.
 6240         "},
 6241        cpp_language.clone(),
 6242        &mut cx,
 6243    );
 6244
 6245    // Test multiple cursors in different paragraphs trigger separate rewraps
 6246    assert_rewrap(
 6247        indoc! {"
 6248            // ˇThis is the first paragraph, first line.
 6249            // ˇThis is the first paragraph, second line.
 6250
 6251            // ˇThis is the second paragraph, first line.
 6252            // ˇThis is the second paragraph, second line.
 6253        "},
 6254        indoc! {"
 6255            // ˇThis is the first paragraph, first
 6256            // line. ˇThis is the first paragraph,
 6257            // second line.
 6258
 6259            // ˇThis is the second paragraph, first
 6260            // line. ˇThis is the second paragraph,
 6261            // second line.
 6262        "},
 6263        cpp_language.clone(),
 6264        &mut cx,
 6265    );
 6266
 6267    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 6268    assert_rewrap(
 6269        indoc! {"
 6270            «// A regular long long comment to be wrapped.
 6271            /// A documentation long comment to be wrapped.ˇ»
 6272          "},
 6273        indoc! {"
 6274            «// A regular long long comment to be
 6275            // wrapped.
 6276            /// A documentation long comment to be
 6277            /// wrapped.ˇ»
 6278          "},
 6279        rust_language.clone(),
 6280        &mut cx,
 6281    );
 6282
 6283    // Test that change in indentation level trigger seperate rewraps
 6284    assert_rewrap(
 6285        indoc! {"
 6286            fn foo() {
 6287                «// This is a long comment at the base indent.
 6288                    // This is a long comment at the next indent.ˇ»
 6289            }
 6290        "},
 6291        indoc! {"
 6292            fn foo() {
 6293                «// This is a long comment at the
 6294                // base indent.
 6295                    // This is a long comment at the
 6296                    // next indent.ˇ»
 6297            }
 6298        "},
 6299        rust_language.clone(),
 6300        &mut cx,
 6301    );
 6302
 6303    // Test that different comment prefix characters (e.g., '#') are handled correctly
 6304    assert_rewrap(
 6305        indoc! {"
 6306            # ˇThis is a long comment using a pound sign.
 6307        "},
 6308        indoc! {"
 6309            # ˇThis is a long comment using a pound
 6310            # sign.
 6311        "},
 6312        python_language,
 6313        &mut cx,
 6314    );
 6315
 6316    // Test rewrapping only affects comments, not code even when selected
 6317    assert_rewrap(
 6318        indoc! {"
 6319            «/// This doc comment is long and should be wrapped.
 6320            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6321        "},
 6322        indoc! {"
 6323            «/// This doc comment is long and should
 6324            /// be wrapped.
 6325            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6326        "},
 6327        rust_language.clone(),
 6328        &mut cx,
 6329    );
 6330
 6331    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 6332    assert_rewrap(
 6333        indoc! {"
 6334            # Header
 6335
 6336            A long long long line of markdown text to wrap.ˇ
 6337         "},
 6338        indoc! {"
 6339            # Header
 6340
 6341            A long long long line of markdown text
 6342            to wrap.ˇ
 6343         "},
 6344        markdown_language.clone(),
 6345        &mut cx,
 6346    );
 6347
 6348    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 6349    assert_rewrap(
 6350        indoc! {"
 6351            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 6352            2. This is a numbered list item that is very long and needs to be wrapped properly.
 6353            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 6354        "},
 6355        indoc! {"
 6356            «1. This is a numbered list item that is
 6357               very long and needs to be wrapped
 6358               properly.
 6359            2. This is a numbered list item that is
 6360               very long and needs to be wrapped
 6361               properly.
 6362            - This is an unordered list item that is
 6363              also very long and should not merge
 6364              with the numbered item.ˇ»
 6365        "},
 6366        markdown_language.clone(),
 6367        &mut cx,
 6368    );
 6369
 6370    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 6371    assert_rewrap(
 6372        indoc! {"
 6373            «1. This is a numbered list item that is
 6374            very long and needs to be wrapped
 6375            properly.
 6376            2. This is a numbered list item that is
 6377            very long and needs to be wrapped
 6378            properly.
 6379            - This is an unordered list item that is
 6380            also very long and should not merge with
 6381            the numbered item.ˇ»
 6382        "},
 6383        indoc! {"
 6384            «1. This is a numbered list item that is
 6385               very long and needs to be wrapped
 6386               properly.
 6387            2. This is a numbered list item that is
 6388               very long and needs to be wrapped
 6389               properly.
 6390            - This is an unordered list item that is
 6391              also very long and should not merge
 6392              with the numbered item.ˇ»
 6393        "},
 6394        markdown_language.clone(),
 6395        &mut cx,
 6396    );
 6397
 6398    // Test that rewrapping maintain indents even when they already exists.
 6399    assert_rewrap(
 6400        indoc! {"
 6401            «1. This is a numbered list
 6402               item that is very long and needs to be wrapped properly.
 6403            2. This is a numbered list
 6404               item that is very long and needs to be wrapped properly.
 6405            - This is an unordered list item that is also very long and
 6406              should not merge with the numbered item.ˇ»
 6407        "},
 6408        indoc! {"
 6409            «1. This is a numbered list item that is
 6410               very long and needs to be wrapped
 6411               properly.
 6412            2. This is a numbered list item that is
 6413               very long and needs to be wrapped
 6414               properly.
 6415            - This is an unordered list item that is
 6416              also very long and should not merge
 6417              with the numbered item.ˇ»
 6418        "},
 6419        markdown_language,
 6420        &mut cx,
 6421    );
 6422
 6423    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 6424    assert_rewrap(
 6425        indoc! {"
 6426            ˇThis is a very long line of plain text that will be wrapped.
 6427        "},
 6428        indoc! {"
 6429            ˇThis is a very long line of plain text
 6430            that will be wrapped.
 6431        "},
 6432        plaintext_language.clone(),
 6433        &mut cx,
 6434    );
 6435
 6436    // Test that non-commented code acts as a paragraph boundary within a selection
 6437    assert_rewrap(
 6438        indoc! {"
 6439               «// This is the first long comment block to be wrapped.
 6440               fn my_func(a: u32);
 6441               // This is the second long comment block to be wrapped.ˇ»
 6442           "},
 6443        indoc! {"
 6444               «// This is the first long comment block
 6445               // to be wrapped.
 6446               fn my_func(a: u32);
 6447               // This is the second long comment block
 6448               // to be wrapped.ˇ»
 6449           "},
 6450        rust_language,
 6451        &mut cx,
 6452    );
 6453
 6454    // Test rewrapping multiple selections, including ones with blank lines or tabs
 6455    assert_rewrap(
 6456        indoc! {"
 6457            «ˇThis is a very long line that will be wrapped.
 6458
 6459            This is another paragraph in the same selection.»
 6460
 6461            «\tThis is a very long indented line that will be wrapped.ˇ»
 6462         "},
 6463        indoc! {"
 6464            «ˇThis is a very long line that will be
 6465            wrapped.
 6466
 6467            This is another paragraph in the same
 6468            selection.»
 6469
 6470            «\tThis is a very long indented line
 6471            \tthat will be wrapped.ˇ»
 6472         "},
 6473        plaintext_language,
 6474        &mut cx,
 6475    );
 6476
 6477    // Test that an empty comment line acts as a paragraph boundary
 6478    assert_rewrap(
 6479        indoc! {"
 6480            // ˇThis is a long comment that will be wrapped.
 6481            //
 6482            // And this is another long comment that will also be wrapped.ˇ
 6483         "},
 6484        indoc! {"
 6485            // ˇThis is a long comment that will be
 6486            // wrapped.
 6487            //
 6488            // And this is another long comment that
 6489            // will also be wrapped.ˇ
 6490         "},
 6491        cpp_language,
 6492        &mut cx,
 6493    );
 6494
 6495    #[track_caller]
 6496    fn assert_rewrap(
 6497        unwrapped_text: &str,
 6498        wrapped_text: &str,
 6499        language: Arc<Language>,
 6500        cx: &mut EditorTestContext,
 6501    ) {
 6502        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6503        cx.set_state(unwrapped_text);
 6504        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6505        cx.assert_editor_state(wrapped_text);
 6506    }
 6507}
 6508
 6509#[gpui::test]
 6510async fn test_rewrap_block_comments(cx: &mut TestAppContext) {
 6511    init_test(cx, |settings| {
 6512        settings.languages.0.extend([(
 6513            "Rust".into(),
 6514            LanguageSettingsContent {
 6515                allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6516                preferred_line_length: Some(40),
 6517                ..Default::default()
 6518            },
 6519        )])
 6520    });
 6521
 6522    let mut cx = EditorTestContext::new(cx).await;
 6523
 6524    let rust_lang = Arc::new(
 6525        Language::new(
 6526            LanguageConfig {
 6527                name: "Rust".into(),
 6528                line_comments: vec!["// ".into()],
 6529                block_comment: Some(BlockCommentConfig {
 6530                    start: "/*".into(),
 6531                    end: "*/".into(),
 6532                    prefix: "* ".into(),
 6533                    tab_size: 1,
 6534                }),
 6535                documentation_comment: Some(BlockCommentConfig {
 6536                    start: "/**".into(),
 6537                    end: "*/".into(),
 6538                    prefix: "* ".into(),
 6539                    tab_size: 1,
 6540                }),
 6541
 6542                ..LanguageConfig::default()
 6543            },
 6544            Some(tree_sitter_rust::LANGUAGE.into()),
 6545        )
 6546        .with_override_query("[(line_comment) (block_comment)] @comment.inclusive")
 6547        .unwrap(),
 6548    );
 6549
 6550    // regular block comment
 6551    assert_rewrap(
 6552        indoc! {"
 6553            /*
 6554             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6555             */
 6556            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6557        "},
 6558        indoc! {"
 6559            /*
 6560             *ˇ Lorem ipsum dolor sit amet,
 6561             * consectetur adipiscing elit.
 6562             */
 6563            /*
 6564             *ˇ Lorem ipsum dolor sit amet,
 6565             * consectetur adipiscing elit.
 6566             */
 6567        "},
 6568        rust_lang.clone(),
 6569        &mut cx,
 6570    );
 6571
 6572    // indent is respected
 6573    assert_rewrap(
 6574        indoc! {"
 6575            {}
 6576                /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6577        "},
 6578        indoc! {"
 6579            {}
 6580                /*
 6581                 *ˇ Lorem ipsum dolor sit amet,
 6582                 * consectetur adipiscing elit.
 6583                 */
 6584        "},
 6585        rust_lang.clone(),
 6586        &mut cx,
 6587    );
 6588
 6589    // short block comments with inline delimiters
 6590    assert_rewrap(
 6591        indoc! {"
 6592            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6593            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6594             */
 6595            /*
 6596             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6597        "},
 6598        indoc! {"
 6599            /*
 6600             *ˇ Lorem ipsum dolor sit amet,
 6601             * consectetur adipiscing elit.
 6602             */
 6603            /*
 6604             *ˇ Lorem ipsum dolor sit amet,
 6605             * consectetur adipiscing elit.
 6606             */
 6607            /*
 6608             *ˇ Lorem ipsum dolor sit amet,
 6609             * consectetur adipiscing elit.
 6610             */
 6611        "},
 6612        rust_lang.clone(),
 6613        &mut cx,
 6614    );
 6615
 6616    // multiline block comment with inline start/end delimiters
 6617    assert_rewrap(
 6618        indoc! {"
 6619            /*ˇ Lorem ipsum dolor sit amet,
 6620             * consectetur adipiscing elit. */
 6621        "},
 6622        indoc! {"
 6623            /*
 6624             *ˇ Lorem ipsum dolor sit amet,
 6625             * consectetur adipiscing elit.
 6626             */
 6627        "},
 6628        rust_lang.clone(),
 6629        &mut cx,
 6630    );
 6631
 6632    // block comment rewrap still respects paragraph bounds
 6633    assert_rewrap(
 6634        indoc! {"
 6635            /*
 6636             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6637             *
 6638             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6639             */
 6640        "},
 6641        indoc! {"
 6642            /*
 6643             *ˇ Lorem ipsum dolor sit amet,
 6644             * consectetur adipiscing elit.
 6645             *
 6646             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6647             */
 6648        "},
 6649        rust_lang.clone(),
 6650        &mut cx,
 6651    );
 6652
 6653    // documentation comments
 6654    assert_rewrap(
 6655        indoc! {"
 6656            /**ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6657            /**
 6658             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6659             */
 6660        "},
 6661        indoc! {"
 6662            /**
 6663             *ˇ Lorem ipsum dolor sit amet,
 6664             * consectetur adipiscing elit.
 6665             */
 6666            /**
 6667             *ˇ Lorem ipsum dolor sit amet,
 6668             * consectetur adipiscing elit.
 6669             */
 6670        "},
 6671        rust_lang.clone(),
 6672        &mut cx,
 6673    );
 6674
 6675    // different, adjacent comments
 6676    assert_rewrap(
 6677        indoc! {"
 6678            /**
 6679             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6680             */
 6681            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6682            //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6683        "},
 6684        indoc! {"
 6685            /**
 6686             *ˇ Lorem ipsum dolor sit amet,
 6687             * consectetur adipiscing elit.
 6688             */
 6689            /*
 6690             *ˇ Lorem ipsum dolor sit amet,
 6691             * consectetur adipiscing elit.
 6692             */
 6693            //ˇ Lorem ipsum dolor sit amet,
 6694            // consectetur adipiscing elit.
 6695        "},
 6696        rust_lang.clone(),
 6697        &mut cx,
 6698    );
 6699
 6700    // selection w/ single short block comment
 6701    assert_rewrap(
 6702        indoc! {"
 6703            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6704        "},
 6705        indoc! {"
 6706            «/*
 6707             * Lorem ipsum dolor sit amet,
 6708             * consectetur adipiscing elit.
 6709             */ˇ»
 6710        "},
 6711        rust_lang.clone(),
 6712        &mut cx,
 6713    );
 6714
 6715    // rewrapping a single comment w/ abutting comments
 6716    assert_rewrap(
 6717        indoc! {"
 6718            /* ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6719            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6720        "},
 6721        indoc! {"
 6722            /*
 6723             * ˇLorem ipsum dolor sit amet,
 6724             * consectetur adipiscing elit.
 6725             */
 6726            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6727        "},
 6728        rust_lang.clone(),
 6729        &mut cx,
 6730    );
 6731
 6732    // selection w/ non-abutting short block comments
 6733    assert_rewrap(
 6734        indoc! {"
 6735            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6736
 6737            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6738        "},
 6739        indoc! {"
 6740            «/*
 6741             * Lorem ipsum dolor sit amet,
 6742             * consectetur adipiscing elit.
 6743             */
 6744
 6745            /*
 6746             * Lorem ipsum dolor sit amet,
 6747             * consectetur adipiscing elit.
 6748             */ˇ»
 6749        "},
 6750        rust_lang.clone(),
 6751        &mut cx,
 6752    );
 6753
 6754    // selection of multiline block comments
 6755    assert_rewrap(
 6756        indoc! {"
 6757            «/* Lorem ipsum dolor sit amet,
 6758             * consectetur adipiscing elit. */ˇ»
 6759        "},
 6760        indoc! {"
 6761            «/*
 6762             * Lorem ipsum dolor sit amet,
 6763             * consectetur adipiscing elit.
 6764             */ˇ»
 6765        "},
 6766        rust_lang.clone(),
 6767        &mut cx,
 6768    );
 6769
 6770    // partial selection of multiline block comments
 6771    assert_rewrap(
 6772        indoc! {"
 6773            «/* Lorem ipsum dolor sit amet,ˇ»
 6774             * consectetur adipiscing elit. */
 6775            /* Lorem ipsum dolor sit amet,
 6776             «* consectetur adipiscing elit. */ˇ»
 6777        "},
 6778        indoc! {"
 6779            «/*
 6780             * Lorem ipsum dolor sit amet,ˇ»
 6781             * consectetur adipiscing elit. */
 6782            /* Lorem ipsum dolor sit amet,
 6783             «* consectetur adipiscing elit.
 6784             */ˇ»
 6785        "},
 6786        rust_lang.clone(),
 6787        &mut cx,
 6788    );
 6789
 6790    // selection w/ abutting short block comments
 6791    // TODO: should not be combined; should rewrap as 2 comments
 6792    assert_rewrap(
 6793        indoc! {"
 6794            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6795            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6796        "},
 6797        // desired behavior:
 6798        // indoc! {"
 6799        //     «/*
 6800        //      * Lorem ipsum dolor sit amet,
 6801        //      * consectetur adipiscing elit.
 6802        //      */
 6803        //     /*
 6804        //      * Lorem ipsum dolor sit amet,
 6805        //      * consectetur adipiscing elit.
 6806        //      */ˇ»
 6807        // "},
 6808        // actual behaviour:
 6809        indoc! {"
 6810            «/*
 6811             * Lorem ipsum dolor sit amet,
 6812             * consectetur adipiscing elit. Lorem
 6813             * ipsum dolor sit amet, consectetur
 6814             * adipiscing elit.
 6815             */ˇ»
 6816        "},
 6817        rust_lang.clone(),
 6818        &mut cx,
 6819    );
 6820
 6821    // TODO: same as above, but with delimiters on separate line
 6822    // assert_rewrap(
 6823    //     indoc! {"
 6824    //         «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6825    //          */
 6826    //         /*
 6827    //          * Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6828    //     "},
 6829    //     // desired:
 6830    //     // indoc! {"
 6831    //     //     «/*
 6832    //     //      * Lorem ipsum dolor sit amet,
 6833    //     //      * consectetur adipiscing elit.
 6834    //     //      */
 6835    //     //     /*
 6836    //     //      * Lorem ipsum dolor sit amet,
 6837    //     //      * consectetur adipiscing elit.
 6838    //     //      */ˇ»
 6839    //     // "},
 6840    //     // actual: (but with trailing w/s on the empty lines)
 6841    //     indoc! {"
 6842    //         «/*
 6843    //          * Lorem ipsum dolor sit amet,
 6844    //          * consectetur adipiscing elit.
 6845    //          *
 6846    //          */
 6847    //         /*
 6848    //          *
 6849    //          * Lorem ipsum dolor sit amet,
 6850    //          * consectetur adipiscing elit.
 6851    //          */ˇ»
 6852    //     "},
 6853    //     rust_lang.clone(),
 6854    //     &mut cx,
 6855    // );
 6856
 6857    // TODO these are unhandled edge cases; not correct, just documenting known issues
 6858    assert_rewrap(
 6859        indoc! {"
 6860            /*
 6861             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6862             */
 6863            /*
 6864             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6865            /*ˇ Lorem ipsum dolor sit amet */ /* consectetur adipiscing elit. */
 6866        "},
 6867        // desired:
 6868        // indoc! {"
 6869        //     /*
 6870        //      *ˇ Lorem ipsum dolor sit amet,
 6871        //      * consectetur adipiscing elit.
 6872        //      */
 6873        //     /*
 6874        //      *ˇ Lorem ipsum dolor sit amet,
 6875        //      * consectetur adipiscing elit.
 6876        //      */
 6877        //     /*
 6878        //      *ˇ Lorem ipsum dolor sit amet
 6879        //      */ /* consectetur adipiscing elit. */
 6880        // "},
 6881        // actual:
 6882        indoc! {"
 6883            /*
 6884             //ˇ Lorem ipsum dolor sit amet,
 6885             // consectetur adipiscing elit.
 6886             */
 6887            /*
 6888             * //ˇ Lorem ipsum dolor sit amet,
 6889             * consectetur adipiscing elit.
 6890             */
 6891            /*
 6892             *ˇ Lorem ipsum dolor sit amet */ /*
 6893             * consectetur adipiscing elit.
 6894             */
 6895        "},
 6896        rust_lang,
 6897        &mut cx,
 6898    );
 6899
 6900    #[track_caller]
 6901    fn assert_rewrap(
 6902        unwrapped_text: &str,
 6903        wrapped_text: &str,
 6904        language: Arc<Language>,
 6905        cx: &mut EditorTestContext,
 6906    ) {
 6907        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6908        cx.set_state(unwrapped_text);
 6909        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6910        cx.assert_editor_state(wrapped_text);
 6911    }
 6912}
 6913
 6914#[gpui::test]
 6915async fn test_hard_wrap(cx: &mut TestAppContext) {
 6916    init_test(cx, |_| {});
 6917    let mut cx = EditorTestContext::new(cx).await;
 6918
 6919    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 6920    cx.update_editor(|editor, _, cx| {
 6921        editor.set_hard_wrap(Some(14), cx);
 6922    });
 6923
 6924    cx.set_state(indoc!(
 6925        "
 6926        one two three ˇ
 6927        "
 6928    ));
 6929    cx.simulate_input("four");
 6930    cx.run_until_parked();
 6931
 6932    cx.assert_editor_state(indoc!(
 6933        "
 6934        one two three
 6935        fourˇ
 6936        "
 6937    ));
 6938
 6939    cx.update_editor(|editor, window, cx| {
 6940        editor.newline(&Default::default(), window, cx);
 6941    });
 6942    cx.run_until_parked();
 6943    cx.assert_editor_state(indoc!(
 6944        "
 6945        one two three
 6946        four
 6947        ˇ
 6948        "
 6949    ));
 6950
 6951    cx.simulate_input("five");
 6952    cx.run_until_parked();
 6953    cx.assert_editor_state(indoc!(
 6954        "
 6955        one two three
 6956        four
 6957        fiveˇ
 6958        "
 6959    ));
 6960
 6961    cx.update_editor(|editor, window, cx| {
 6962        editor.newline(&Default::default(), window, cx);
 6963    });
 6964    cx.run_until_parked();
 6965    cx.simulate_input("# ");
 6966    cx.run_until_parked();
 6967    cx.assert_editor_state(indoc!(
 6968        "
 6969        one two three
 6970        four
 6971        five
 6972        # ˇ
 6973        "
 6974    ));
 6975
 6976    cx.update_editor(|editor, window, cx| {
 6977        editor.newline(&Default::default(), window, cx);
 6978    });
 6979    cx.run_until_parked();
 6980    cx.assert_editor_state(indoc!(
 6981        "
 6982        one two three
 6983        four
 6984        five
 6985        #\x20
 6986 6987        "
 6988    ));
 6989
 6990    cx.simulate_input(" 6");
 6991    cx.run_until_parked();
 6992    cx.assert_editor_state(indoc!(
 6993        "
 6994        one two three
 6995        four
 6996        five
 6997        #
 6998        # 6ˇ
 6999        "
 7000    ));
 7001}
 7002
 7003#[gpui::test]
 7004async fn test_cut_line_ends(cx: &mut TestAppContext) {
 7005    init_test(cx, |_| {});
 7006
 7007    let mut cx = EditorTestContext::new(cx).await;
 7008
 7009    cx.set_state(indoc! {"The quick brownˇ"});
 7010    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 7011    cx.assert_editor_state(indoc! {"The quick brownˇ"});
 7012
 7013    cx.set_state(indoc! {"The emacs foxˇ"});
 7014    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 7015    cx.assert_editor_state(indoc! {"The emacs foxˇ"});
 7016
 7017    cx.set_state(indoc! {"
 7018        The quick« brownˇ»
 7019        fox jumps overˇ
 7020        the lazy dog"});
 7021    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7022    cx.assert_editor_state(indoc! {"
 7023        The quickˇ
 7024        ˇthe lazy dog"});
 7025
 7026    cx.set_state(indoc! {"
 7027        The quick« brownˇ»
 7028        fox jumps overˇ
 7029        the lazy dog"});
 7030    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 7031    cx.assert_editor_state(indoc! {"
 7032        The quickˇ
 7033        fox jumps overˇthe lazy dog"});
 7034
 7035    cx.set_state(indoc! {"
 7036        The quick« brownˇ»
 7037        fox jumps overˇ
 7038        the lazy dog"});
 7039    cx.update_editor(|e, window, cx| {
 7040        e.cut_to_end_of_line(
 7041            &CutToEndOfLine {
 7042                stop_at_newlines: true,
 7043            },
 7044            window,
 7045            cx,
 7046        )
 7047    });
 7048    cx.assert_editor_state(indoc! {"
 7049        The quickˇ
 7050        fox jumps overˇ
 7051        the lazy dog"});
 7052
 7053    cx.set_state(indoc! {"
 7054        The quick« brownˇ»
 7055        fox jumps overˇ
 7056        the lazy dog"});
 7057    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 7058    cx.assert_editor_state(indoc! {"
 7059        The quickˇ
 7060        fox jumps overˇthe lazy dog"});
 7061}
 7062
 7063#[gpui::test]
 7064async fn test_clipboard(cx: &mut TestAppContext) {
 7065    init_test(cx, |_| {});
 7066
 7067    let mut cx = EditorTestContext::new(cx).await;
 7068
 7069    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 7070    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7071    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 7072
 7073    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 7074    cx.set_state("two ˇfour ˇsix ˇ");
 7075    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7076    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 7077
 7078    // Paste again but with only two cursors. Since the number of cursors doesn't
 7079    // match the number of slices in the clipboard, the entire clipboard text
 7080    // is pasted at each cursor.
 7081    cx.set_state("ˇtwo one✅ four three six five ˇ");
 7082    cx.update_editor(|e, window, cx| {
 7083        e.handle_input("( ", window, cx);
 7084        e.paste(&Paste, window, cx);
 7085        e.handle_input(") ", window, cx);
 7086    });
 7087    cx.assert_editor_state(
 7088        &([
 7089            "( one✅ ",
 7090            "three ",
 7091            "five ) ˇtwo one✅ four three six five ( one✅ ",
 7092            "three ",
 7093            "five ) ˇ",
 7094        ]
 7095        .join("\n")),
 7096    );
 7097
 7098    // Cut with three selections, one of which is full-line.
 7099    cx.set_state(indoc! {"
 7100        1«2ˇ»3
 7101        4ˇ567
 7102        «8ˇ»9"});
 7103    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7104    cx.assert_editor_state(indoc! {"
 7105        1ˇ3
 7106        ˇ9"});
 7107
 7108    // Paste with three selections, noticing how the copied selection that was full-line
 7109    // gets inserted before the second cursor.
 7110    cx.set_state(indoc! {"
 7111        1ˇ3
 7112 7113        «oˇ»ne"});
 7114    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7115    cx.assert_editor_state(indoc! {"
 7116        12ˇ3
 7117        4567
 7118 7119        8ˇne"});
 7120
 7121    // Copy with a single cursor only, which writes the whole line into the clipboard.
 7122    cx.set_state(indoc! {"
 7123        The quick brown
 7124        fox juˇmps over
 7125        the lazy dog"});
 7126    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7127    assert_eq!(
 7128        cx.read_from_clipboard()
 7129            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7130        Some("fox jumps over\n".to_string())
 7131    );
 7132
 7133    // Paste with three selections, noticing how the copied full-line selection is inserted
 7134    // before the empty selections but replaces the selection that is non-empty.
 7135    cx.set_state(indoc! {"
 7136        Tˇhe quick brown
 7137        «foˇ»x jumps over
 7138        tˇhe lazy dog"});
 7139    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7140    cx.assert_editor_state(indoc! {"
 7141        fox jumps over
 7142        Tˇhe quick brown
 7143        fox jumps over
 7144        ˇx jumps over
 7145        fox jumps over
 7146        tˇhe lazy dog"});
 7147}
 7148
 7149#[gpui::test]
 7150async fn test_copy_trim(cx: &mut TestAppContext) {
 7151    init_test(cx, |_| {});
 7152
 7153    let mut cx = EditorTestContext::new(cx).await;
 7154    cx.set_state(
 7155        r#"            «for selection in selections.iter() {
 7156            let mut start = selection.start;
 7157            let mut end = selection.end;
 7158            let is_entire_line = selection.is_empty();
 7159            if is_entire_line {
 7160                start = Point::new(start.row, 0);ˇ»
 7161                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7162            }
 7163        "#,
 7164    );
 7165    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7166    assert_eq!(
 7167        cx.read_from_clipboard()
 7168            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7169        Some(
 7170            "for selection in selections.iter() {
 7171            let mut start = selection.start;
 7172            let mut end = selection.end;
 7173            let is_entire_line = selection.is_empty();
 7174            if is_entire_line {
 7175                start = Point::new(start.row, 0);"
 7176                .to_string()
 7177        ),
 7178        "Regular copying preserves all indentation selected",
 7179    );
 7180    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7181    assert_eq!(
 7182        cx.read_from_clipboard()
 7183            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7184        Some(
 7185            "for selection in selections.iter() {
 7186let mut start = selection.start;
 7187let mut end = selection.end;
 7188let is_entire_line = selection.is_empty();
 7189if is_entire_line {
 7190    start = Point::new(start.row, 0);"
 7191                .to_string()
 7192        ),
 7193        "Copying with stripping should strip all leading whitespaces"
 7194    );
 7195
 7196    cx.set_state(
 7197        r#"       «     for selection in selections.iter() {
 7198            let mut start = selection.start;
 7199            let mut end = selection.end;
 7200            let is_entire_line = selection.is_empty();
 7201            if is_entire_line {
 7202                start = Point::new(start.row, 0);ˇ»
 7203                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7204            }
 7205        "#,
 7206    );
 7207    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7208    assert_eq!(
 7209        cx.read_from_clipboard()
 7210            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7211        Some(
 7212            "     for selection in selections.iter() {
 7213            let mut start = selection.start;
 7214            let mut end = selection.end;
 7215            let is_entire_line = selection.is_empty();
 7216            if is_entire_line {
 7217                start = Point::new(start.row, 0);"
 7218                .to_string()
 7219        ),
 7220        "Regular copying preserves all indentation selected",
 7221    );
 7222    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7223    assert_eq!(
 7224        cx.read_from_clipboard()
 7225            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7226        Some(
 7227            "for selection in selections.iter() {
 7228let mut start = selection.start;
 7229let mut end = selection.end;
 7230let is_entire_line = selection.is_empty();
 7231if is_entire_line {
 7232    start = Point::new(start.row, 0);"
 7233                .to_string()
 7234        ),
 7235        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 7236    );
 7237
 7238    cx.set_state(
 7239        r#"       «ˇ     for selection in selections.iter() {
 7240            let mut start = selection.start;
 7241            let mut end = selection.end;
 7242            let is_entire_line = selection.is_empty();
 7243            if is_entire_line {
 7244                start = Point::new(start.row, 0);»
 7245                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7246            }
 7247        "#,
 7248    );
 7249    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7250    assert_eq!(
 7251        cx.read_from_clipboard()
 7252            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7253        Some(
 7254            "     for selection in selections.iter() {
 7255            let mut start = selection.start;
 7256            let mut end = selection.end;
 7257            let is_entire_line = selection.is_empty();
 7258            if is_entire_line {
 7259                start = Point::new(start.row, 0);"
 7260                .to_string()
 7261        ),
 7262        "Regular copying for reverse selection works the same",
 7263    );
 7264    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7265    assert_eq!(
 7266        cx.read_from_clipboard()
 7267            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7268        Some(
 7269            "for selection in selections.iter() {
 7270let mut start = selection.start;
 7271let mut end = selection.end;
 7272let is_entire_line = selection.is_empty();
 7273if is_entire_line {
 7274    start = Point::new(start.row, 0);"
 7275                .to_string()
 7276        ),
 7277        "Copying with stripping for reverse selection works the same"
 7278    );
 7279
 7280    cx.set_state(
 7281        r#"            for selection «in selections.iter() {
 7282            let mut start = selection.start;
 7283            let mut end = selection.end;
 7284            let is_entire_line = selection.is_empty();
 7285            if is_entire_line {
 7286                start = Point::new(start.row, 0);ˇ»
 7287                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7288            }
 7289        "#,
 7290    );
 7291    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7292    assert_eq!(
 7293        cx.read_from_clipboard()
 7294            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7295        Some(
 7296            "in selections.iter() {
 7297            let mut start = selection.start;
 7298            let mut end = selection.end;
 7299            let is_entire_line = selection.is_empty();
 7300            if is_entire_line {
 7301                start = Point::new(start.row, 0);"
 7302                .to_string()
 7303        ),
 7304        "When selecting past the indent, the copying works as usual",
 7305    );
 7306    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7307    assert_eq!(
 7308        cx.read_from_clipboard()
 7309            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7310        Some(
 7311            "in selections.iter() {
 7312            let mut start = selection.start;
 7313            let mut end = selection.end;
 7314            let is_entire_line = selection.is_empty();
 7315            if is_entire_line {
 7316                start = Point::new(start.row, 0);"
 7317                .to_string()
 7318        ),
 7319        "When selecting past the indent, nothing is trimmed"
 7320    );
 7321
 7322    cx.set_state(
 7323        r#"            «for selection in selections.iter() {
 7324            let mut start = selection.start;
 7325
 7326            let mut end = selection.end;
 7327            let is_entire_line = selection.is_empty();
 7328            if is_entire_line {
 7329                start = Point::new(start.row, 0);
 7330ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7331            }
 7332        "#,
 7333    );
 7334    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7335    assert_eq!(
 7336        cx.read_from_clipboard()
 7337            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7338        Some(
 7339            "for selection in selections.iter() {
 7340let mut start = selection.start;
 7341
 7342let mut end = selection.end;
 7343let is_entire_line = selection.is_empty();
 7344if is_entire_line {
 7345    start = Point::new(start.row, 0);
 7346"
 7347            .to_string()
 7348        ),
 7349        "Copying with stripping should ignore empty lines"
 7350    );
 7351}
 7352
 7353#[gpui::test]
 7354async fn test_paste_multiline(cx: &mut TestAppContext) {
 7355    init_test(cx, |_| {});
 7356
 7357    let mut cx = EditorTestContext::new(cx).await;
 7358    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7359
 7360    // Cut an indented block, without the leading whitespace.
 7361    cx.set_state(indoc! {"
 7362        const a: B = (
 7363            c(),
 7364            «d(
 7365                e,
 7366                f
 7367            )ˇ»
 7368        );
 7369    "});
 7370    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7371    cx.assert_editor_state(indoc! {"
 7372        const a: B = (
 7373            c(),
 7374            ˇ
 7375        );
 7376    "});
 7377
 7378    // Paste it at the same position.
 7379    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7380    cx.assert_editor_state(indoc! {"
 7381        const a: B = (
 7382            c(),
 7383            d(
 7384                e,
 7385                f
 7386 7387        );
 7388    "});
 7389
 7390    // Paste it at a line with a lower indent level.
 7391    cx.set_state(indoc! {"
 7392        ˇ
 7393        const a: B = (
 7394            c(),
 7395        );
 7396    "});
 7397    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7398    cx.assert_editor_state(indoc! {"
 7399        d(
 7400            e,
 7401            f
 7402 7403        const a: B = (
 7404            c(),
 7405        );
 7406    "});
 7407
 7408    // Cut an indented block, with the leading whitespace.
 7409    cx.set_state(indoc! {"
 7410        const a: B = (
 7411            c(),
 7412        «    d(
 7413                e,
 7414                f
 7415            )
 7416        ˇ»);
 7417    "});
 7418    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7419    cx.assert_editor_state(indoc! {"
 7420        const a: B = (
 7421            c(),
 7422        ˇ);
 7423    "});
 7424
 7425    // Paste it at the same position.
 7426    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7427    cx.assert_editor_state(indoc! {"
 7428        const a: B = (
 7429            c(),
 7430            d(
 7431                e,
 7432                f
 7433            )
 7434        ˇ);
 7435    "});
 7436
 7437    // Paste it at a line with a higher indent level.
 7438    cx.set_state(indoc! {"
 7439        const a: B = (
 7440            c(),
 7441            d(
 7442                e,
 7443 7444            )
 7445        );
 7446    "});
 7447    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7448    cx.assert_editor_state(indoc! {"
 7449        const a: B = (
 7450            c(),
 7451            d(
 7452                e,
 7453                f    d(
 7454                    e,
 7455                    f
 7456                )
 7457        ˇ
 7458            )
 7459        );
 7460    "});
 7461
 7462    // Copy an indented block, starting mid-line
 7463    cx.set_state(indoc! {"
 7464        const a: B = (
 7465            c(),
 7466            somethin«g(
 7467                e,
 7468                f
 7469            )ˇ»
 7470        );
 7471    "});
 7472    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7473
 7474    // Paste it on a line with a lower indent level
 7475    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 7476    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7477    cx.assert_editor_state(indoc! {"
 7478        const a: B = (
 7479            c(),
 7480            something(
 7481                e,
 7482                f
 7483            )
 7484        );
 7485        g(
 7486            e,
 7487            f
 7488"});
 7489}
 7490
 7491#[gpui::test]
 7492async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 7493    init_test(cx, |_| {});
 7494
 7495    cx.write_to_clipboard(ClipboardItem::new_string(
 7496        "    d(\n        e\n    );\n".into(),
 7497    ));
 7498
 7499    let mut cx = EditorTestContext::new(cx).await;
 7500    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7501
 7502    cx.set_state(indoc! {"
 7503        fn a() {
 7504            b();
 7505            if c() {
 7506                ˇ
 7507            }
 7508        }
 7509    "});
 7510
 7511    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7512    cx.assert_editor_state(indoc! {"
 7513        fn a() {
 7514            b();
 7515            if c() {
 7516                d(
 7517                    e
 7518                );
 7519        ˇ
 7520            }
 7521        }
 7522    "});
 7523
 7524    cx.set_state(indoc! {"
 7525        fn a() {
 7526            b();
 7527            ˇ
 7528        }
 7529    "});
 7530
 7531    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7532    cx.assert_editor_state(indoc! {"
 7533        fn a() {
 7534            b();
 7535            d(
 7536                e
 7537            );
 7538        ˇ
 7539        }
 7540    "});
 7541}
 7542
 7543#[gpui::test]
 7544fn test_select_all(cx: &mut TestAppContext) {
 7545    init_test(cx, |_| {});
 7546
 7547    let editor = cx.add_window(|window, cx| {
 7548        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 7549        build_editor(buffer, window, cx)
 7550    });
 7551    _ = editor.update(cx, |editor, window, cx| {
 7552        editor.select_all(&SelectAll, window, cx);
 7553        assert_eq!(
 7554            display_ranges(editor, cx),
 7555            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 7556        );
 7557    });
 7558}
 7559
 7560#[gpui::test]
 7561fn test_select_line(cx: &mut TestAppContext) {
 7562    init_test(cx, |_| {});
 7563
 7564    let editor = cx.add_window(|window, cx| {
 7565        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 7566        build_editor(buffer, window, cx)
 7567    });
 7568    _ = editor.update(cx, |editor, window, cx| {
 7569        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7570            s.select_display_ranges([
 7571                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7572                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7573                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7574                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 7575            ])
 7576        });
 7577        editor.select_line(&SelectLine, window, cx);
 7578        assert_eq!(
 7579            display_ranges(editor, cx),
 7580            vec![
 7581                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 7582                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 7583            ]
 7584        );
 7585    });
 7586
 7587    _ = editor.update(cx, |editor, window, cx| {
 7588        editor.select_line(&SelectLine, window, cx);
 7589        assert_eq!(
 7590            display_ranges(editor, cx),
 7591            vec![
 7592                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 7593                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 7594            ]
 7595        );
 7596    });
 7597
 7598    _ = editor.update(cx, |editor, window, cx| {
 7599        editor.select_line(&SelectLine, window, cx);
 7600        assert_eq!(
 7601            display_ranges(editor, cx),
 7602            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 7603        );
 7604    });
 7605}
 7606
 7607#[gpui::test]
 7608async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 7609    init_test(cx, |_| {});
 7610    let mut cx = EditorTestContext::new(cx).await;
 7611
 7612    #[track_caller]
 7613    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 7614        cx.set_state(initial_state);
 7615        cx.update_editor(|e, window, cx| {
 7616            e.split_selection_into_lines(&Default::default(), window, cx)
 7617        });
 7618        cx.assert_editor_state(expected_state);
 7619    }
 7620
 7621    // Selection starts and ends at the middle of lines, left-to-right
 7622    test(
 7623        &mut cx,
 7624        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 7625        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7626    );
 7627    // Same thing, right-to-left
 7628    test(
 7629        &mut cx,
 7630        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 7631        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7632    );
 7633
 7634    // Whole buffer, left-to-right, last line *doesn't* end with newline
 7635    test(
 7636        &mut cx,
 7637        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 7638        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7639    );
 7640    // Same thing, right-to-left
 7641    test(
 7642        &mut cx,
 7643        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 7644        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7645    );
 7646
 7647    // Whole buffer, left-to-right, last line ends with newline
 7648    test(
 7649        &mut cx,
 7650        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 7651        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7652    );
 7653    // Same thing, right-to-left
 7654    test(
 7655        &mut cx,
 7656        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 7657        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7658    );
 7659
 7660    // Starts at the end of a line, ends at the start of another
 7661    test(
 7662        &mut cx,
 7663        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 7664        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 7665    );
 7666}
 7667
 7668#[gpui::test]
 7669async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 7670    init_test(cx, |_| {});
 7671
 7672    let editor = cx.add_window(|window, cx| {
 7673        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 7674        build_editor(buffer, window, cx)
 7675    });
 7676
 7677    // setup
 7678    _ = editor.update(cx, |editor, window, cx| {
 7679        editor.fold_creases(
 7680            vec![
 7681                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 7682                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 7683                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 7684            ],
 7685            true,
 7686            window,
 7687            cx,
 7688        );
 7689        assert_eq!(
 7690            editor.display_text(cx),
 7691            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7692        );
 7693    });
 7694
 7695    _ = editor.update(cx, |editor, window, cx| {
 7696        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7697            s.select_display_ranges([
 7698                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7699                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7700                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7701                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 7702            ])
 7703        });
 7704        editor.split_selection_into_lines(&Default::default(), window, cx);
 7705        assert_eq!(
 7706            editor.display_text(cx),
 7707            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7708        );
 7709    });
 7710    EditorTestContext::for_editor(editor, cx)
 7711        .await
 7712        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 7713
 7714    _ = editor.update(cx, |editor, window, cx| {
 7715        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7716            s.select_display_ranges([
 7717                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 7718            ])
 7719        });
 7720        editor.split_selection_into_lines(&Default::default(), window, cx);
 7721        assert_eq!(
 7722            editor.display_text(cx),
 7723            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 7724        );
 7725        assert_eq!(
 7726            display_ranges(editor, cx),
 7727            [
 7728                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 7729                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 7730                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 7731                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 7732                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 7733                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 7734                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 7735            ]
 7736        );
 7737    });
 7738    EditorTestContext::for_editor(editor, cx)
 7739        .await
 7740        .assert_editor_state(
 7741            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 7742        );
 7743}
 7744
 7745#[gpui::test]
 7746async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 7747    init_test(cx, |_| {});
 7748
 7749    let mut cx = EditorTestContext::new(cx).await;
 7750
 7751    cx.set_state(indoc!(
 7752        r#"abc
 7753           defˇghi
 7754
 7755           jk
 7756           nlmo
 7757           "#
 7758    ));
 7759
 7760    cx.update_editor(|editor, window, cx| {
 7761        editor.add_selection_above(&Default::default(), window, cx);
 7762    });
 7763
 7764    cx.assert_editor_state(indoc!(
 7765        r#"abcˇ
 7766           defˇghi
 7767
 7768           jk
 7769           nlmo
 7770           "#
 7771    ));
 7772
 7773    cx.update_editor(|editor, window, cx| {
 7774        editor.add_selection_above(&Default::default(), window, cx);
 7775    });
 7776
 7777    cx.assert_editor_state(indoc!(
 7778        r#"abcˇ
 7779            defˇghi
 7780
 7781            jk
 7782            nlmo
 7783            "#
 7784    ));
 7785
 7786    cx.update_editor(|editor, window, cx| {
 7787        editor.add_selection_below(&Default::default(), window, cx);
 7788    });
 7789
 7790    cx.assert_editor_state(indoc!(
 7791        r#"abc
 7792           defˇghi
 7793
 7794           jk
 7795           nlmo
 7796           "#
 7797    ));
 7798
 7799    cx.update_editor(|editor, window, cx| {
 7800        editor.undo_selection(&Default::default(), window, cx);
 7801    });
 7802
 7803    cx.assert_editor_state(indoc!(
 7804        r#"abcˇ
 7805           defˇghi
 7806
 7807           jk
 7808           nlmo
 7809           "#
 7810    ));
 7811
 7812    cx.update_editor(|editor, window, cx| {
 7813        editor.redo_selection(&Default::default(), window, cx);
 7814    });
 7815
 7816    cx.assert_editor_state(indoc!(
 7817        r#"abc
 7818           defˇghi
 7819
 7820           jk
 7821           nlmo
 7822           "#
 7823    ));
 7824
 7825    cx.update_editor(|editor, window, cx| {
 7826        editor.add_selection_below(&Default::default(), window, cx);
 7827    });
 7828
 7829    cx.assert_editor_state(indoc!(
 7830        r#"abc
 7831           defˇghi
 7832           ˇ
 7833           jk
 7834           nlmo
 7835           "#
 7836    ));
 7837
 7838    cx.update_editor(|editor, window, cx| {
 7839        editor.add_selection_below(&Default::default(), window, cx);
 7840    });
 7841
 7842    cx.assert_editor_state(indoc!(
 7843        r#"abc
 7844           defˇghi
 7845           ˇ
 7846           jkˇ
 7847           nlmo
 7848           "#
 7849    ));
 7850
 7851    cx.update_editor(|editor, window, cx| {
 7852        editor.add_selection_below(&Default::default(), window, cx);
 7853    });
 7854
 7855    cx.assert_editor_state(indoc!(
 7856        r#"abc
 7857           defˇghi
 7858           ˇ
 7859           jkˇ
 7860           nlmˇo
 7861           "#
 7862    ));
 7863
 7864    cx.update_editor(|editor, window, cx| {
 7865        editor.add_selection_below(&Default::default(), window, cx);
 7866    });
 7867
 7868    cx.assert_editor_state(indoc!(
 7869        r#"abc
 7870           defˇghi
 7871           ˇ
 7872           jkˇ
 7873           nlmˇo
 7874           ˇ"#
 7875    ));
 7876
 7877    // change selections
 7878    cx.set_state(indoc!(
 7879        r#"abc
 7880           def«ˇg»hi
 7881
 7882           jk
 7883           nlmo
 7884           "#
 7885    ));
 7886
 7887    cx.update_editor(|editor, window, cx| {
 7888        editor.add_selection_below(&Default::default(), window, cx);
 7889    });
 7890
 7891    cx.assert_editor_state(indoc!(
 7892        r#"abc
 7893           def«ˇg»hi
 7894
 7895           jk
 7896           nlm«ˇo»
 7897           "#
 7898    ));
 7899
 7900    cx.update_editor(|editor, window, cx| {
 7901        editor.add_selection_below(&Default::default(), window, cx);
 7902    });
 7903
 7904    cx.assert_editor_state(indoc!(
 7905        r#"abc
 7906           def«ˇg»hi
 7907
 7908           jk
 7909           nlm«ˇo»
 7910           "#
 7911    ));
 7912
 7913    cx.update_editor(|editor, window, cx| {
 7914        editor.add_selection_above(&Default::default(), window, cx);
 7915    });
 7916
 7917    cx.assert_editor_state(indoc!(
 7918        r#"abc
 7919           def«ˇg»hi
 7920
 7921           jk
 7922           nlmo
 7923           "#
 7924    ));
 7925
 7926    cx.update_editor(|editor, window, cx| {
 7927        editor.add_selection_above(&Default::default(), window, cx);
 7928    });
 7929
 7930    cx.assert_editor_state(indoc!(
 7931        r#"abc
 7932           def«ˇg»hi
 7933
 7934           jk
 7935           nlmo
 7936           "#
 7937    ));
 7938
 7939    // Change selections again
 7940    cx.set_state(indoc!(
 7941        r#"a«bc
 7942           defgˇ»hi
 7943
 7944           jk
 7945           nlmo
 7946           "#
 7947    ));
 7948
 7949    cx.update_editor(|editor, window, cx| {
 7950        editor.add_selection_below(&Default::default(), window, cx);
 7951    });
 7952
 7953    cx.assert_editor_state(indoc!(
 7954        r#"a«bcˇ»
 7955           d«efgˇ»hi
 7956
 7957           j«kˇ»
 7958           nlmo
 7959           "#
 7960    ));
 7961
 7962    cx.update_editor(|editor, window, cx| {
 7963        editor.add_selection_below(&Default::default(), window, cx);
 7964    });
 7965    cx.assert_editor_state(indoc!(
 7966        r#"a«bcˇ»
 7967           d«efgˇ»hi
 7968
 7969           j«kˇ»
 7970           n«lmoˇ»
 7971           "#
 7972    ));
 7973    cx.update_editor(|editor, window, cx| {
 7974        editor.add_selection_above(&Default::default(), window, cx);
 7975    });
 7976
 7977    cx.assert_editor_state(indoc!(
 7978        r#"a«bcˇ»
 7979           d«efgˇ»hi
 7980
 7981           j«kˇ»
 7982           nlmo
 7983           "#
 7984    ));
 7985
 7986    // Change selections again
 7987    cx.set_state(indoc!(
 7988        r#"abc
 7989           d«ˇefghi
 7990
 7991           jk
 7992           nlm»o
 7993           "#
 7994    ));
 7995
 7996    cx.update_editor(|editor, window, cx| {
 7997        editor.add_selection_above(&Default::default(), window, cx);
 7998    });
 7999
 8000    cx.assert_editor_state(indoc!(
 8001        r#"a«ˇbc»
 8002           d«ˇef»ghi
 8003
 8004           j«ˇk»
 8005           n«ˇlm»o
 8006           "#
 8007    ));
 8008
 8009    cx.update_editor(|editor, window, cx| {
 8010        editor.add_selection_below(&Default::default(), window, cx);
 8011    });
 8012
 8013    cx.assert_editor_state(indoc!(
 8014        r#"abc
 8015           d«ˇef»ghi
 8016
 8017           j«ˇk»
 8018           n«ˇlm»o
 8019           "#
 8020    ));
 8021}
 8022
 8023#[gpui::test]
 8024async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 8025    init_test(cx, |_| {});
 8026    let mut cx = EditorTestContext::new(cx).await;
 8027
 8028    cx.set_state(indoc!(
 8029        r#"line onˇe
 8030           liˇne two
 8031           line three
 8032           line four"#
 8033    ));
 8034
 8035    cx.update_editor(|editor, window, cx| {
 8036        editor.add_selection_below(&Default::default(), window, cx);
 8037    });
 8038
 8039    // test multiple cursors expand in the same direction
 8040    cx.assert_editor_state(indoc!(
 8041        r#"line onˇe
 8042           liˇne twˇo
 8043           liˇne three
 8044           line four"#
 8045    ));
 8046
 8047    cx.update_editor(|editor, window, cx| {
 8048        editor.add_selection_below(&Default::default(), window, cx);
 8049    });
 8050
 8051    cx.update_editor(|editor, window, cx| {
 8052        editor.add_selection_below(&Default::default(), window, cx);
 8053    });
 8054
 8055    // test multiple cursors expand below overflow
 8056    cx.assert_editor_state(indoc!(
 8057        r#"line onˇe
 8058           liˇne twˇo
 8059           liˇne thˇree
 8060           liˇne foˇur"#
 8061    ));
 8062
 8063    cx.update_editor(|editor, window, cx| {
 8064        editor.add_selection_above(&Default::default(), window, cx);
 8065    });
 8066
 8067    // test multiple cursors retrieves back correctly
 8068    cx.assert_editor_state(indoc!(
 8069        r#"line onˇe
 8070           liˇne twˇo
 8071           liˇne thˇree
 8072           line four"#
 8073    ));
 8074
 8075    cx.update_editor(|editor, window, cx| {
 8076        editor.add_selection_above(&Default::default(), window, cx);
 8077    });
 8078
 8079    cx.update_editor(|editor, window, cx| {
 8080        editor.add_selection_above(&Default::default(), window, cx);
 8081    });
 8082
 8083    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 8084    cx.assert_editor_state(indoc!(
 8085        r#"liˇne onˇe
 8086           liˇne two
 8087           line three
 8088           line four"#
 8089    ));
 8090
 8091    cx.update_editor(|editor, window, cx| {
 8092        editor.undo_selection(&Default::default(), window, cx);
 8093    });
 8094
 8095    // test undo
 8096    cx.assert_editor_state(indoc!(
 8097        r#"line onˇe
 8098           liˇne twˇo
 8099           line three
 8100           line four"#
 8101    ));
 8102
 8103    cx.update_editor(|editor, window, cx| {
 8104        editor.redo_selection(&Default::default(), window, cx);
 8105    });
 8106
 8107    // test redo
 8108    cx.assert_editor_state(indoc!(
 8109        r#"liˇne onˇe
 8110           liˇne two
 8111           line three
 8112           line four"#
 8113    ));
 8114
 8115    cx.set_state(indoc!(
 8116        r#"abcd
 8117           ef«ghˇ»
 8118           ijkl
 8119           «mˇ»nop"#
 8120    ));
 8121
 8122    cx.update_editor(|editor, window, cx| {
 8123        editor.add_selection_above(&Default::default(), window, cx);
 8124    });
 8125
 8126    // test multiple selections expand in the same direction
 8127    cx.assert_editor_state(indoc!(
 8128        r#"ab«cdˇ»
 8129           ef«ghˇ»
 8130           «iˇ»jkl
 8131           «mˇ»nop"#
 8132    ));
 8133
 8134    cx.update_editor(|editor, window, cx| {
 8135        editor.add_selection_above(&Default::default(), window, cx);
 8136    });
 8137
 8138    // test multiple selection upward overflow
 8139    cx.assert_editor_state(indoc!(
 8140        r#"ab«cdˇ»
 8141           «eˇ»f«ghˇ»
 8142           «iˇ»jkl
 8143           «mˇ»nop"#
 8144    ));
 8145
 8146    cx.update_editor(|editor, window, cx| {
 8147        editor.add_selection_below(&Default::default(), window, cx);
 8148    });
 8149
 8150    // test multiple selection retrieves back correctly
 8151    cx.assert_editor_state(indoc!(
 8152        r#"abcd
 8153           ef«ghˇ»
 8154           «iˇ»jkl
 8155           «mˇ»nop"#
 8156    ));
 8157
 8158    cx.update_editor(|editor, window, cx| {
 8159        editor.add_selection_below(&Default::default(), window, cx);
 8160    });
 8161
 8162    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 8163    cx.assert_editor_state(indoc!(
 8164        r#"abcd
 8165           ef«ghˇ»
 8166           ij«klˇ»
 8167           «mˇ»nop"#
 8168    ));
 8169
 8170    cx.update_editor(|editor, window, cx| {
 8171        editor.undo_selection(&Default::default(), window, cx);
 8172    });
 8173
 8174    // test undo
 8175    cx.assert_editor_state(indoc!(
 8176        r#"abcd
 8177           ef«ghˇ»
 8178           «iˇ»jkl
 8179           «mˇ»nop"#
 8180    ));
 8181
 8182    cx.update_editor(|editor, window, cx| {
 8183        editor.redo_selection(&Default::default(), window, cx);
 8184    });
 8185
 8186    // test redo
 8187    cx.assert_editor_state(indoc!(
 8188        r#"abcd
 8189           ef«ghˇ»
 8190           ij«klˇ»
 8191           «mˇ»nop"#
 8192    ));
 8193}
 8194
 8195#[gpui::test]
 8196async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 8197    init_test(cx, |_| {});
 8198    let mut cx = EditorTestContext::new(cx).await;
 8199
 8200    cx.set_state(indoc!(
 8201        r#"line onˇe
 8202           liˇne two
 8203           line three
 8204           line four"#
 8205    ));
 8206
 8207    cx.update_editor(|editor, window, cx| {
 8208        editor.add_selection_below(&Default::default(), window, cx);
 8209        editor.add_selection_below(&Default::default(), window, cx);
 8210        editor.add_selection_below(&Default::default(), window, cx);
 8211    });
 8212
 8213    // initial state with two multi cursor groups
 8214    cx.assert_editor_state(indoc!(
 8215        r#"line onˇe
 8216           liˇne twˇo
 8217           liˇne thˇree
 8218           liˇne foˇur"#
 8219    ));
 8220
 8221    // add single cursor in middle - simulate opt click
 8222    cx.update_editor(|editor, window, cx| {
 8223        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 8224        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8225        editor.end_selection(window, cx);
 8226    });
 8227
 8228    cx.assert_editor_state(indoc!(
 8229        r#"line onˇe
 8230           liˇne twˇo
 8231           liˇneˇ thˇree
 8232           liˇne foˇur"#
 8233    ));
 8234
 8235    cx.update_editor(|editor, window, cx| {
 8236        editor.add_selection_above(&Default::default(), window, cx);
 8237    });
 8238
 8239    // test new added selection expands above and existing selection shrinks
 8240    cx.assert_editor_state(indoc!(
 8241        r#"line onˇe
 8242           liˇneˇ twˇo
 8243           liˇneˇ thˇree
 8244           line four"#
 8245    ));
 8246
 8247    cx.update_editor(|editor, window, cx| {
 8248        editor.add_selection_above(&Default::default(), window, cx);
 8249    });
 8250
 8251    // test new added selection expands above and existing selection shrinks
 8252    cx.assert_editor_state(indoc!(
 8253        r#"lineˇ onˇe
 8254           liˇneˇ twˇo
 8255           lineˇ three
 8256           line four"#
 8257    ));
 8258
 8259    // intial state with two selection groups
 8260    cx.set_state(indoc!(
 8261        r#"abcd
 8262           ef«ghˇ»
 8263           ijkl
 8264           «mˇ»nop"#
 8265    ));
 8266
 8267    cx.update_editor(|editor, window, cx| {
 8268        editor.add_selection_above(&Default::default(), window, cx);
 8269        editor.add_selection_above(&Default::default(), window, cx);
 8270    });
 8271
 8272    cx.assert_editor_state(indoc!(
 8273        r#"ab«cdˇ»
 8274           «eˇ»f«ghˇ»
 8275           «iˇ»jkl
 8276           «mˇ»nop"#
 8277    ));
 8278
 8279    // add single selection in middle - simulate opt drag
 8280    cx.update_editor(|editor, window, cx| {
 8281        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 8282        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8283        editor.update_selection(
 8284            DisplayPoint::new(DisplayRow(2), 4),
 8285            0,
 8286            gpui::Point::<f32>::default(),
 8287            window,
 8288            cx,
 8289        );
 8290        editor.end_selection(window, cx);
 8291    });
 8292
 8293    cx.assert_editor_state(indoc!(
 8294        r#"ab«cdˇ»
 8295           «eˇ»f«ghˇ»
 8296           «iˇ»jk«lˇ»
 8297           «mˇ»nop"#
 8298    ));
 8299
 8300    cx.update_editor(|editor, window, cx| {
 8301        editor.add_selection_below(&Default::default(), window, cx);
 8302    });
 8303
 8304    // test new added selection expands below, others shrinks from above
 8305    cx.assert_editor_state(indoc!(
 8306        r#"abcd
 8307           ef«ghˇ»
 8308           «iˇ»jk«lˇ»
 8309           «mˇ»no«pˇ»"#
 8310    ));
 8311}
 8312
 8313#[gpui::test]
 8314async fn test_select_next(cx: &mut TestAppContext) {
 8315    init_test(cx, |_| {});
 8316
 8317    let mut cx = EditorTestContext::new(cx).await;
 8318    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8319
 8320    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8321        .unwrap();
 8322    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8323
 8324    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8325        .unwrap();
 8326    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8327
 8328    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8329    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8330
 8331    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8332    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8333
 8334    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8335        .unwrap();
 8336    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8337
 8338    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8339        .unwrap();
 8340    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8341
 8342    // Test selection direction should be preserved
 8343    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8344
 8345    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8346        .unwrap();
 8347    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 8348}
 8349
 8350#[gpui::test]
 8351async fn test_select_all_matches(cx: &mut TestAppContext) {
 8352    init_test(cx, |_| {});
 8353
 8354    let mut cx = EditorTestContext::new(cx).await;
 8355
 8356    // Test caret-only selections
 8357    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8358    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8359        .unwrap();
 8360    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8361
 8362    // Test left-to-right selections
 8363    cx.set_state("abc\n«abcˇ»\nabc");
 8364    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8365        .unwrap();
 8366    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 8367
 8368    // Test right-to-left selections
 8369    cx.set_state("abc\n«ˇabc»\nabc");
 8370    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8371        .unwrap();
 8372    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 8373
 8374    // Test selecting whitespace with caret selection
 8375    cx.set_state("abc\nˇ   abc\nabc");
 8376    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8377        .unwrap();
 8378    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 8379
 8380    // Test selecting whitespace with left-to-right selection
 8381    cx.set_state("abc\n«ˇ  »abc\nabc");
 8382    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8383        .unwrap();
 8384    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 8385
 8386    // Test no matches with right-to-left selection
 8387    cx.set_state("abc\n«  ˇ»abc\nabc");
 8388    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8389        .unwrap();
 8390    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 8391
 8392    // Test with a single word and clip_at_line_ends=true (#29823)
 8393    cx.set_state("aˇbc");
 8394    cx.update_editor(|e, window, cx| {
 8395        e.set_clip_at_line_ends(true, cx);
 8396        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8397        e.set_clip_at_line_ends(false, cx);
 8398    });
 8399    cx.assert_editor_state("«abcˇ»");
 8400}
 8401
 8402#[gpui::test]
 8403async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 8404    init_test(cx, |_| {});
 8405
 8406    let mut cx = EditorTestContext::new(cx).await;
 8407
 8408    let large_body_1 = "\nd".repeat(200);
 8409    let large_body_2 = "\ne".repeat(200);
 8410
 8411    cx.set_state(&format!(
 8412        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 8413    ));
 8414    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 8415        let scroll_position = editor.scroll_position(cx);
 8416        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 8417        scroll_position
 8418    });
 8419
 8420    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8421        .unwrap();
 8422    cx.assert_editor_state(&format!(
 8423        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 8424    ));
 8425    let scroll_position_after_selection =
 8426        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 8427    assert_eq!(
 8428        initial_scroll_position, scroll_position_after_selection,
 8429        "Scroll position should not change after selecting all matches"
 8430    );
 8431}
 8432
 8433#[gpui::test]
 8434async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 8435    init_test(cx, |_| {});
 8436
 8437    let mut cx = EditorLspTestContext::new_rust(
 8438        lsp::ServerCapabilities {
 8439            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8440            ..Default::default()
 8441        },
 8442        cx,
 8443    )
 8444    .await;
 8445
 8446    cx.set_state(indoc! {"
 8447        line 1
 8448        line 2
 8449        linˇe 3
 8450        line 4
 8451        line 5
 8452    "});
 8453
 8454    // Make an edit
 8455    cx.update_editor(|editor, window, cx| {
 8456        editor.handle_input("X", window, cx);
 8457    });
 8458
 8459    // Move cursor to a different position
 8460    cx.update_editor(|editor, window, cx| {
 8461        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8462            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 8463        });
 8464    });
 8465
 8466    cx.assert_editor_state(indoc! {"
 8467        line 1
 8468        line 2
 8469        linXe 3
 8470        line 4
 8471        liˇne 5
 8472    "});
 8473
 8474    cx.lsp
 8475        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 8476            Ok(Some(vec![lsp::TextEdit::new(
 8477                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 8478                "PREFIX ".to_string(),
 8479            )]))
 8480        });
 8481
 8482    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 8483        .unwrap()
 8484        .await
 8485        .unwrap();
 8486
 8487    cx.assert_editor_state(indoc! {"
 8488        PREFIX line 1
 8489        line 2
 8490        linXe 3
 8491        line 4
 8492        liˇne 5
 8493    "});
 8494
 8495    // Undo formatting
 8496    cx.update_editor(|editor, window, cx| {
 8497        editor.undo(&Default::default(), window, cx);
 8498    });
 8499
 8500    // Verify cursor moved back to position after edit
 8501    cx.assert_editor_state(indoc! {"
 8502        line 1
 8503        line 2
 8504        linXˇe 3
 8505        line 4
 8506        line 5
 8507    "});
 8508}
 8509
 8510#[gpui::test]
 8511async fn test_undo_edit_prediction_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 8512    init_test(cx, |_| {});
 8513
 8514    let mut cx = EditorTestContext::new(cx).await;
 8515
 8516    let provider = cx.new(|_| FakeEditPredictionProvider::default());
 8517    cx.update_editor(|editor, window, cx| {
 8518        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 8519    });
 8520
 8521    cx.set_state(indoc! {"
 8522        line 1
 8523        line 2
 8524        linˇe 3
 8525        line 4
 8526        line 5
 8527        line 6
 8528        line 7
 8529        line 8
 8530        line 9
 8531        line 10
 8532    "});
 8533
 8534    let snapshot = cx.buffer_snapshot();
 8535    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 8536
 8537    cx.update(|_, cx| {
 8538        provider.update(cx, |provider, _| {
 8539            provider.set_edit_prediction(Some(edit_prediction::EditPrediction::Local {
 8540                id: None,
 8541                edits: vec![(edit_position..edit_position, "X".into())],
 8542                edit_preview: None,
 8543            }))
 8544        })
 8545    });
 8546
 8547    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 8548    cx.update_editor(|editor, window, cx| {
 8549        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 8550    });
 8551
 8552    cx.assert_editor_state(indoc! {"
 8553        line 1
 8554        line 2
 8555        lineXˇ 3
 8556        line 4
 8557        line 5
 8558        line 6
 8559        line 7
 8560        line 8
 8561        line 9
 8562        line 10
 8563    "});
 8564
 8565    cx.update_editor(|editor, window, cx| {
 8566        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8567            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 8568        });
 8569    });
 8570
 8571    cx.assert_editor_state(indoc! {"
 8572        line 1
 8573        line 2
 8574        lineX 3
 8575        line 4
 8576        line 5
 8577        line 6
 8578        line 7
 8579        line 8
 8580        line 9
 8581        liˇne 10
 8582    "});
 8583
 8584    cx.update_editor(|editor, window, cx| {
 8585        editor.undo(&Default::default(), window, cx);
 8586    });
 8587
 8588    cx.assert_editor_state(indoc! {"
 8589        line 1
 8590        line 2
 8591        lineˇ 3
 8592        line 4
 8593        line 5
 8594        line 6
 8595        line 7
 8596        line 8
 8597        line 9
 8598        line 10
 8599    "});
 8600}
 8601
 8602#[gpui::test]
 8603async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 8604    init_test(cx, |_| {});
 8605
 8606    let mut cx = EditorTestContext::new(cx).await;
 8607    cx.set_state(
 8608        r#"let foo = 2;
 8609lˇet foo = 2;
 8610let fooˇ = 2;
 8611let foo = 2;
 8612let foo = ˇ2;"#,
 8613    );
 8614
 8615    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8616        .unwrap();
 8617    cx.assert_editor_state(
 8618        r#"let foo = 2;
 8619«letˇ» foo = 2;
 8620let «fooˇ» = 2;
 8621let foo = 2;
 8622let foo = «2ˇ»;"#,
 8623    );
 8624
 8625    // noop for multiple selections with different contents
 8626    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8627        .unwrap();
 8628    cx.assert_editor_state(
 8629        r#"let foo = 2;
 8630«letˇ» foo = 2;
 8631let «fooˇ» = 2;
 8632let foo = 2;
 8633let foo = «2ˇ»;"#,
 8634    );
 8635
 8636    // Test last selection direction should be preserved
 8637    cx.set_state(
 8638        r#"let foo = 2;
 8639let foo = 2;
 8640let «fooˇ» = 2;
 8641let «ˇfoo» = 2;
 8642let foo = 2;"#,
 8643    );
 8644
 8645    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8646        .unwrap();
 8647    cx.assert_editor_state(
 8648        r#"let foo = 2;
 8649let foo = 2;
 8650let «fooˇ» = 2;
 8651let «ˇfoo» = 2;
 8652let «ˇfoo» = 2;"#,
 8653    );
 8654}
 8655
 8656#[gpui::test]
 8657async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 8658    init_test(cx, |_| {});
 8659
 8660    let mut cx =
 8661        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 8662
 8663    cx.assert_editor_state(indoc! {"
 8664        ˇbbb
 8665        ccc
 8666
 8667        bbb
 8668        ccc
 8669        "});
 8670    cx.dispatch_action(SelectPrevious::default());
 8671    cx.assert_editor_state(indoc! {"
 8672                «bbbˇ»
 8673                ccc
 8674
 8675                bbb
 8676                ccc
 8677                "});
 8678    cx.dispatch_action(SelectPrevious::default());
 8679    cx.assert_editor_state(indoc! {"
 8680                «bbbˇ»
 8681                ccc
 8682
 8683                «bbbˇ»
 8684                ccc
 8685                "});
 8686}
 8687
 8688#[gpui::test]
 8689async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 8690    init_test(cx, |_| {});
 8691
 8692    let mut cx = EditorTestContext::new(cx).await;
 8693    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8694
 8695    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8696        .unwrap();
 8697    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8698
 8699    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8700        .unwrap();
 8701    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8702
 8703    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8704    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8705
 8706    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8707    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8708
 8709    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8710        .unwrap();
 8711    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 8712
 8713    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8714        .unwrap();
 8715    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8716}
 8717
 8718#[gpui::test]
 8719async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 8720    init_test(cx, |_| {});
 8721
 8722    let mut cx = EditorTestContext::new(cx).await;
 8723    cx.set_state("");
 8724
 8725    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8726        .unwrap();
 8727    cx.assert_editor_state("«aˇ»");
 8728    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8729        .unwrap();
 8730    cx.assert_editor_state("«aˇ»");
 8731}
 8732
 8733#[gpui::test]
 8734async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 8735    init_test(cx, |_| {});
 8736
 8737    let mut cx = EditorTestContext::new(cx).await;
 8738    cx.set_state(
 8739        r#"let foo = 2;
 8740lˇet foo = 2;
 8741let fooˇ = 2;
 8742let foo = 2;
 8743let foo = ˇ2;"#,
 8744    );
 8745
 8746    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8747        .unwrap();
 8748    cx.assert_editor_state(
 8749        r#"let foo = 2;
 8750«letˇ» foo = 2;
 8751let «fooˇ» = 2;
 8752let foo = 2;
 8753let foo = «2ˇ»;"#,
 8754    );
 8755
 8756    // noop for multiple selections with different contents
 8757    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8758        .unwrap();
 8759    cx.assert_editor_state(
 8760        r#"let foo = 2;
 8761«letˇ» foo = 2;
 8762let «fooˇ» = 2;
 8763let foo = 2;
 8764let foo = «2ˇ»;"#,
 8765    );
 8766}
 8767
 8768#[gpui::test]
 8769async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 8770    init_test(cx, |_| {});
 8771
 8772    let mut cx = EditorTestContext::new(cx).await;
 8773    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8774
 8775    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8776        .unwrap();
 8777    // selection direction is preserved
 8778    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8779
 8780    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8781        .unwrap();
 8782    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8783
 8784    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8785    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8786
 8787    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8788    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8789
 8790    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8791        .unwrap();
 8792    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 8793
 8794    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8795        .unwrap();
 8796    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 8797}
 8798
 8799#[gpui::test]
 8800async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 8801    init_test(cx, |_| {});
 8802
 8803    let language = Arc::new(Language::new(
 8804        LanguageConfig::default(),
 8805        Some(tree_sitter_rust::LANGUAGE.into()),
 8806    ));
 8807
 8808    let text = r#"
 8809        use mod1::mod2::{mod3, mod4};
 8810
 8811        fn fn_1(param1: bool, param2: &str) {
 8812            let var1 = "text";
 8813        }
 8814    "#
 8815    .unindent();
 8816
 8817    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8818    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8819    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8820
 8821    editor
 8822        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8823        .await;
 8824
 8825    editor.update_in(cx, |editor, window, cx| {
 8826        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8827            s.select_display_ranges([
 8828                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 8829                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 8830                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 8831            ]);
 8832        });
 8833        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8834    });
 8835    editor.update(cx, |editor, cx| {
 8836        assert_text_with_selections(
 8837            editor,
 8838            indoc! {r#"
 8839                use mod1::mod2::{mod3, «mod4ˇ»};
 8840
 8841                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8842                    let var1 = "«ˇtext»";
 8843                }
 8844            "#},
 8845            cx,
 8846        );
 8847    });
 8848
 8849    editor.update_in(cx, |editor, window, cx| {
 8850        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8851    });
 8852    editor.update(cx, |editor, cx| {
 8853        assert_text_with_selections(
 8854            editor,
 8855            indoc! {r#"
 8856                use mod1::mod2::«{mod3, mod4}ˇ»;
 8857
 8858                «ˇfn fn_1(param1: bool, param2: &str) {
 8859                    let var1 = "text";
 8860 8861            "#},
 8862            cx,
 8863        );
 8864    });
 8865
 8866    editor.update_in(cx, |editor, window, cx| {
 8867        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8868    });
 8869    assert_eq!(
 8870        editor.update(cx, |editor, cx| editor
 8871            .selections
 8872            .display_ranges(&editor.display_snapshot(cx))),
 8873        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8874    );
 8875
 8876    // Trying to expand the selected syntax node one more time has no effect.
 8877    editor.update_in(cx, |editor, window, cx| {
 8878        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8879    });
 8880    assert_eq!(
 8881        editor.update(cx, |editor, cx| editor
 8882            .selections
 8883            .display_ranges(&editor.display_snapshot(cx))),
 8884        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8885    );
 8886
 8887    editor.update_in(cx, |editor, window, cx| {
 8888        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8889    });
 8890    editor.update(cx, |editor, cx| {
 8891        assert_text_with_selections(
 8892            editor,
 8893            indoc! {r#"
 8894                use mod1::mod2::«{mod3, mod4}ˇ»;
 8895
 8896                «ˇfn fn_1(param1: bool, param2: &str) {
 8897                    let var1 = "text";
 8898 8899            "#},
 8900            cx,
 8901        );
 8902    });
 8903
 8904    editor.update_in(cx, |editor, window, cx| {
 8905        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8906    });
 8907    editor.update(cx, |editor, cx| {
 8908        assert_text_with_selections(
 8909            editor,
 8910            indoc! {r#"
 8911                use mod1::mod2::{mod3, «mod4ˇ»};
 8912
 8913                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8914                    let var1 = "«ˇtext»";
 8915                }
 8916            "#},
 8917            cx,
 8918        );
 8919    });
 8920
 8921    editor.update_in(cx, |editor, window, cx| {
 8922        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8923    });
 8924    editor.update(cx, |editor, cx| {
 8925        assert_text_with_selections(
 8926            editor,
 8927            indoc! {r#"
 8928                use mod1::mod2::{mod3, moˇd4};
 8929
 8930                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 8931                    let var1 = "teˇxt";
 8932                }
 8933            "#},
 8934            cx,
 8935        );
 8936    });
 8937
 8938    // Trying to shrink the selected syntax node one more time has no effect.
 8939    editor.update_in(cx, |editor, window, cx| {
 8940        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8941    });
 8942    editor.update_in(cx, |editor, _, cx| {
 8943        assert_text_with_selections(
 8944            editor,
 8945            indoc! {r#"
 8946                use mod1::mod2::{mod3, moˇd4};
 8947
 8948                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 8949                    let var1 = "teˇxt";
 8950                }
 8951            "#},
 8952            cx,
 8953        );
 8954    });
 8955
 8956    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 8957    // a fold.
 8958    editor.update_in(cx, |editor, window, cx| {
 8959        editor.fold_creases(
 8960            vec![
 8961                Crease::simple(
 8962                    Point::new(0, 21)..Point::new(0, 24),
 8963                    FoldPlaceholder::test(),
 8964                ),
 8965                Crease::simple(
 8966                    Point::new(3, 20)..Point::new(3, 22),
 8967                    FoldPlaceholder::test(),
 8968                ),
 8969            ],
 8970            true,
 8971            window,
 8972            cx,
 8973        );
 8974        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8975    });
 8976    editor.update(cx, |editor, cx| {
 8977        assert_text_with_selections(
 8978            editor,
 8979            indoc! {r#"
 8980                use mod1::mod2::«{mod3, mod4}ˇ»;
 8981
 8982                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8983                    let var1 = "«ˇtext»";
 8984                }
 8985            "#},
 8986            cx,
 8987        );
 8988    });
 8989}
 8990
 8991#[gpui::test]
 8992async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 8993    init_test(cx, |_| {});
 8994
 8995    let language = Arc::new(Language::new(
 8996        LanguageConfig::default(),
 8997        Some(tree_sitter_rust::LANGUAGE.into()),
 8998    ));
 8999
 9000    let text = "let a = 2;";
 9001
 9002    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9003    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9004    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9005
 9006    editor
 9007        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9008        .await;
 9009
 9010    // Test case 1: Cursor at end of word
 9011    editor.update_in(cx, |editor, window, cx| {
 9012        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9013            s.select_display_ranges([
 9014                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 9015            ]);
 9016        });
 9017    });
 9018    editor.update(cx, |editor, cx| {
 9019        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 9020    });
 9021    editor.update_in(cx, |editor, window, cx| {
 9022        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9023    });
 9024    editor.update(cx, |editor, cx| {
 9025        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 9026    });
 9027    editor.update_in(cx, |editor, window, cx| {
 9028        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9029    });
 9030    editor.update(cx, |editor, cx| {
 9031        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 9032    });
 9033
 9034    // Test case 2: Cursor at end of statement
 9035    editor.update_in(cx, |editor, window, cx| {
 9036        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9037            s.select_display_ranges([
 9038                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 9039            ]);
 9040        });
 9041    });
 9042    editor.update(cx, |editor, cx| {
 9043        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 9044    });
 9045    editor.update_in(cx, |editor, window, cx| {
 9046        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9047    });
 9048    editor.update(cx, |editor, cx| {
 9049        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 9050    });
 9051}
 9052
 9053#[gpui::test]
 9054async fn test_select_larger_syntax_node_for_cursor_at_symbol(cx: &mut TestAppContext) {
 9055    init_test(cx, |_| {});
 9056
 9057    let language = Arc::new(Language::new(
 9058        LanguageConfig {
 9059            name: "JavaScript".into(),
 9060            ..Default::default()
 9061        },
 9062        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 9063    ));
 9064
 9065    let text = r#"
 9066        let a = {
 9067            key: "value",
 9068        };
 9069    "#
 9070    .unindent();
 9071
 9072    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9073    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9074    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9075
 9076    editor
 9077        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9078        .await;
 9079
 9080    // Test case 1: Cursor after '{'
 9081    editor.update_in(cx, |editor, window, cx| {
 9082        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9083            s.select_display_ranges([
 9084                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 9)
 9085            ]);
 9086        });
 9087    });
 9088    editor.update(cx, |editor, cx| {
 9089        assert_text_with_selections(
 9090            editor,
 9091            indoc! {r#"
 9092                let a = {ˇ
 9093                    key: "value",
 9094                };
 9095            "#},
 9096            cx,
 9097        );
 9098    });
 9099    editor.update_in(cx, |editor, window, cx| {
 9100        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9101    });
 9102    editor.update(cx, |editor, cx| {
 9103        assert_text_with_selections(
 9104            editor,
 9105            indoc! {r#"
 9106                let a = «ˇ{
 9107                    key: "value",
 9108                }»;
 9109            "#},
 9110            cx,
 9111        );
 9112    });
 9113
 9114    // Test case 2: Cursor after ':'
 9115    editor.update_in(cx, |editor, window, cx| {
 9116        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9117            s.select_display_ranges([
 9118                DisplayPoint::new(DisplayRow(1), 8)..DisplayPoint::new(DisplayRow(1), 8)
 9119            ]);
 9120        });
 9121    });
 9122    editor.update(cx, |editor, cx| {
 9123        assert_text_with_selections(
 9124            editor,
 9125            indoc! {r#"
 9126                let a = {
 9127                    key:ˇ "value",
 9128                };
 9129            "#},
 9130            cx,
 9131        );
 9132    });
 9133    editor.update_in(cx, |editor, window, cx| {
 9134        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9135    });
 9136    editor.update(cx, |editor, cx| {
 9137        assert_text_with_selections(
 9138            editor,
 9139            indoc! {r#"
 9140                let a = {
 9141                    «ˇkey: "value"»,
 9142                };
 9143            "#},
 9144            cx,
 9145        );
 9146    });
 9147    editor.update_in(cx, |editor, window, cx| {
 9148        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9149    });
 9150    editor.update(cx, |editor, cx| {
 9151        assert_text_with_selections(
 9152            editor,
 9153            indoc! {r#"
 9154                let a = «ˇ{
 9155                    key: "value",
 9156                }»;
 9157            "#},
 9158            cx,
 9159        );
 9160    });
 9161
 9162    // Test case 3: Cursor after ','
 9163    editor.update_in(cx, |editor, window, cx| {
 9164        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9165            s.select_display_ranges([
 9166                DisplayPoint::new(DisplayRow(1), 17)..DisplayPoint::new(DisplayRow(1), 17)
 9167            ]);
 9168        });
 9169    });
 9170    editor.update(cx, |editor, cx| {
 9171        assert_text_with_selections(
 9172            editor,
 9173            indoc! {r#"
 9174                let a = {
 9175                    key: "value",ˇ
 9176                };
 9177            "#},
 9178            cx,
 9179        );
 9180    });
 9181    editor.update_in(cx, |editor, window, cx| {
 9182        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9183    });
 9184    editor.update(cx, |editor, cx| {
 9185        assert_text_with_selections(
 9186            editor,
 9187            indoc! {r#"
 9188                let a = «ˇ{
 9189                    key: "value",
 9190                }»;
 9191            "#},
 9192            cx,
 9193        );
 9194    });
 9195
 9196    // Test case 4: Cursor after ';'
 9197    editor.update_in(cx, |editor, window, cx| {
 9198        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9199            s.select_display_ranges([
 9200                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)
 9201            ]);
 9202        });
 9203    });
 9204    editor.update(cx, |editor, cx| {
 9205        assert_text_with_selections(
 9206            editor,
 9207            indoc! {r#"
 9208                let a = {
 9209                    key: "value",
 9210                };ˇ
 9211            "#},
 9212            cx,
 9213        );
 9214    });
 9215    editor.update_in(cx, |editor, window, cx| {
 9216        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9217    });
 9218    editor.update(cx, |editor, cx| {
 9219        assert_text_with_selections(
 9220            editor,
 9221            indoc! {r#"
 9222                «ˇlet a = {
 9223                    key: "value",
 9224                };
 9225                »"#},
 9226            cx,
 9227        );
 9228    });
 9229}
 9230
 9231#[gpui::test]
 9232async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 9233    init_test(cx, |_| {});
 9234
 9235    let language = Arc::new(Language::new(
 9236        LanguageConfig::default(),
 9237        Some(tree_sitter_rust::LANGUAGE.into()),
 9238    ));
 9239
 9240    let text = r#"
 9241        use mod1::mod2::{mod3, mod4};
 9242
 9243        fn fn_1(param1: bool, param2: &str) {
 9244            let var1 = "hello world";
 9245        }
 9246    "#
 9247    .unindent();
 9248
 9249    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9250    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9251    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9252
 9253    editor
 9254        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9255        .await;
 9256
 9257    // Test 1: Cursor on a letter of a string word
 9258    editor.update_in(cx, |editor, window, cx| {
 9259        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9260            s.select_display_ranges([
 9261                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 9262            ]);
 9263        });
 9264    });
 9265    editor.update_in(cx, |editor, window, cx| {
 9266        assert_text_with_selections(
 9267            editor,
 9268            indoc! {r#"
 9269                use mod1::mod2::{mod3, mod4};
 9270
 9271                fn fn_1(param1: bool, param2: &str) {
 9272                    let var1 = "hˇello world";
 9273                }
 9274            "#},
 9275            cx,
 9276        );
 9277        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9278        assert_text_with_selections(
 9279            editor,
 9280            indoc! {r#"
 9281                use mod1::mod2::{mod3, mod4};
 9282
 9283                fn fn_1(param1: bool, param2: &str) {
 9284                    let var1 = "«ˇhello» world";
 9285                }
 9286            "#},
 9287            cx,
 9288        );
 9289    });
 9290
 9291    // Test 2: Partial selection within a word
 9292    editor.update_in(cx, |editor, window, cx| {
 9293        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9294            s.select_display_ranges([
 9295                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 9296            ]);
 9297        });
 9298    });
 9299    editor.update_in(cx, |editor, window, cx| {
 9300        assert_text_with_selections(
 9301            editor,
 9302            indoc! {r#"
 9303                use mod1::mod2::{mod3, mod4};
 9304
 9305                fn fn_1(param1: bool, param2: &str) {
 9306                    let var1 = "h«elˇ»lo world";
 9307                }
 9308            "#},
 9309            cx,
 9310        );
 9311        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9312        assert_text_with_selections(
 9313            editor,
 9314            indoc! {r#"
 9315                use mod1::mod2::{mod3, mod4};
 9316
 9317                fn fn_1(param1: bool, param2: &str) {
 9318                    let var1 = "«ˇhello» world";
 9319                }
 9320            "#},
 9321            cx,
 9322        );
 9323    });
 9324
 9325    // Test 3: Complete word already selected
 9326    editor.update_in(cx, |editor, window, cx| {
 9327        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9328            s.select_display_ranges([
 9329                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 9330            ]);
 9331        });
 9332    });
 9333    editor.update_in(cx, |editor, window, cx| {
 9334        assert_text_with_selections(
 9335            editor,
 9336            indoc! {r#"
 9337                use mod1::mod2::{mod3, mod4};
 9338
 9339                fn fn_1(param1: bool, param2: &str) {
 9340                    let var1 = "«helloˇ» world";
 9341                }
 9342            "#},
 9343            cx,
 9344        );
 9345        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9346        assert_text_with_selections(
 9347            editor,
 9348            indoc! {r#"
 9349                use mod1::mod2::{mod3, mod4};
 9350
 9351                fn fn_1(param1: bool, param2: &str) {
 9352                    let var1 = "«hello worldˇ»";
 9353                }
 9354            "#},
 9355            cx,
 9356        );
 9357    });
 9358
 9359    // Test 4: Selection spanning across words
 9360    editor.update_in(cx, |editor, window, cx| {
 9361        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9362            s.select_display_ranges([
 9363                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 9364            ]);
 9365        });
 9366    });
 9367    editor.update_in(cx, |editor, window, cx| {
 9368        assert_text_with_selections(
 9369            editor,
 9370            indoc! {r#"
 9371                use mod1::mod2::{mod3, mod4};
 9372
 9373                fn fn_1(param1: bool, param2: &str) {
 9374                    let var1 = "hel«lo woˇ»rld";
 9375                }
 9376            "#},
 9377            cx,
 9378        );
 9379        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9380        assert_text_with_selections(
 9381            editor,
 9382            indoc! {r#"
 9383                use mod1::mod2::{mod3, mod4};
 9384
 9385                fn fn_1(param1: bool, param2: &str) {
 9386                    let var1 = "«ˇhello world»";
 9387                }
 9388            "#},
 9389            cx,
 9390        );
 9391    });
 9392
 9393    // Test 5: Expansion beyond string
 9394    editor.update_in(cx, |editor, window, cx| {
 9395        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9396        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9397        assert_text_with_selections(
 9398            editor,
 9399            indoc! {r#"
 9400                use mod1::mod2::{mod3, mod4};
 9401
 9402                fn fn_1(param1: bool, param2: &str) {
 9403                    «ˇlet var1 = "hello world";»
 9404                }
 9405            "#},
 9406            cx,
 9407        );
 9408    });
 9409}
 9410
 9411#[gpui::test]
 9412async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) {
 9413    init_test(cx, |_| {});
 9414
 9415    let mut cx = EditorTestContext::new(cx).await;
 9416
 9417    let language = Arc::new(Language::new(
 9418        LanguageConfig::default(),
 9419        Some(tree_sitter_rust::LANGUAGE.into()),
 9420    ));
 9421
 9422    cx.update_buffer(|buffer, cx| {
 9423        buffer.set_language(Some(language), cx);
 9424    });
 9425
 9426    cx.set_state(indoc! { r#"use mod1::{mod2::{«mod3ˇ», mod4}, mod5::{mod6, «mod7ˇ»}};"# });
 9427    cx.update_editor(|editor, window, cx| {
 9428        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9429    });
 9430
 9431    cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# });
 9432
 9433    cx.set_state(indoc! { r#"fn a() {
 9434          // what
 9435          // a
 9436          // ˇlong
 9437          // method
 9438          // I
 9439          // sure
 9440          // hope
 9441          // it
 9442          // works
 9443    }"# });
 9444
 9445    let buffer = cx.update_multibuffer(|multibuffer, _| multibuffer.as_singleton().unwrap());
 9446    let multi_buffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 9447    cx.update(|_, cx| {
 9448        multi_buffer.update(cx, |multi_buffer, cx| {
 9449            multi_buffer.set_excerpts_for_path(
 9450                PathKey::for_buffer(&buffer, cx),
 9451                buffer,
 9452                [Point::new(1, 0)..Point::new(1, 0)],
 9453                3,
 9454                cx,
 9455            );
 9456        });
 9457    });
 9458
 9459    let editor2 = cx.new_window_entity(|window, cx| {
 9460        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
 9461    });
 9462
 9463    let mut cx = EditorTestContext::for_editor_in(editor2, &mut cx).await;
 9464    cx.update_editor(|editor, window, cx| {
 9465        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 9466            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)]);
 9467        })
 9468    });
 9469
 9470    cx.assert_editor_state(indoc! { "
 9471        fn a() {
 9472              // what
 9473              // a
 9474        ˇ      // long
 9475              // method"});
 9476
 9477    cx.update_editor(|editor, window, cx| {
 9478        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9479    });
 9480
 9481    // Although we could potentially make the action work when the syntax node
 9482    // is half-hidden, it seems a bit dangerous as you can't easily tell what it
 9483    // did. Maybe we could also expand the excerpt to contain the range?
 9484    cx.assert_editor_state(indoc! { "
 9485        fn a() {
 9486              // what
 9487              // a
 9488        ˇ      // long
 9489              // method"});
 9490}
 9491
 9492#[gpui::test]
 9493async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 9494    init_test(cx, |_| {});
 9495
 9496    let base_text = r#"
 9497        impl A {
 9498            // this is an uncommitted comment
 9499
 9500            fn b() {
 9501                c();
 9502            }
 9503
 9504            // this is another uncommitted comment
 9505
 9506            fn d() {
 9507                // e
 9508                // f
 9509            }
 9510        }
 9511
 9512        fn g() {
 9513            // h
 9514        }
 9515    "#
 9516    .unindent();
 9517
 9518    let text = r#"
 9519        ˇimpl A {
 9520
 9521            fn b() {
 9522                c();
 9523            }
 9524
 9525            fn d() {
 9526                // e
 9527                // f
 9528            }
 9529        }
 9530
 9531        fn g() {
 9532            // h
 9533        }
 9534    "#
 9535    .unindent();
 9536
 9537    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9538    cx.set_state(&text);
 9539    cx.set_head_text(&base_text);
 9540    cx.update_editor(|editor, window, cx| {
 9541        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 9542    });
 9543
 9544    cx.assert_state_with_diff(
 9545        "
 9546        ˇimpl A {
 9547      -     // this is an uncommitted comment
 9548
 9549            fn b() {
 9550                c();
 9551            }
 9552
 9553      -     // this is another uncommitted comment
 9554      -
 9555            fn d() {
 9556                // e
 9557                // f
 9558            }
 9559        }
 9560
 9561        fn g() {
 9562            // h
 9563        }
 9564    "
 9565        .unindent(),
 9566    );
 9567
 9568    let expected_display_text = "
 9569        impl A {
 9570            // this is an uncommitted comment
 9571
 9572            fn b() {
 9573 9574            }
 9575
 9576            // this is another uncommitted comment
 9577
 9578            fn d() {
 9579 9580            }
 9581        }
 9582
 9583        fn g() {
 9584 9585        }
 9586        "
 9587    .unindent();
 9588
 9589    cx.update_editor(|editor, window, cx| {
 9590        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 9591        assert_eq!(editor.display_text(cx), expected_display_text);
 9592    });
 9593}
 9594
 9595#[gpui::test]
 9596async fn test_autoindent(cx: &mut TestAppContext) {
 9597    init_test(cx, |_| {});
 9598
 9599    let language = Arc::new(
 9600        Language::new(
 9601            LanguageConfig {
 9602                brackets: BracketPairConfig {
 9603                    pairs: vec![
 9604                        BracketPair {
 9605                            start: "{".to_string(),
 9606                            end: "}".to_string(),
 9607                            close: false,
 9608                            surround: false,
 9609                            newline: true,
 9610                        },
 9611                        BracketPair {
 9612                            start: "(".to_string(),
 9613                            end: ")".to_string(),
 9614                            close: false,
 9615                            surround: false,
 9616                            newline: true,
 9617                        },
 9618                    ],
 9619                    ..Default::default()
 9620                },
 9621                ..Default::default()
 9622            },
 9623            Some(tree_sitter_rust::LANGUAGE.into()),
 9624        )
 9625        .with_indents_query(
 9626            r#"
 9627                (_ "(" ")" @end) @indent
 9628                (_ "{" "}" @end) @indent
 9629            "#,
 9630        )
 9631        .unwrap(),
 9632    );
 9633
 9634    let text = "fn a() {}";
 9635
 9636    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9637    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9638    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9639    editor
 9640        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9641        .await;
 9642
 9643    editor.update_in(cx, |editor, window, cx| {
 9644        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9645            s.select_ranges([5..5, 8..8, 9..9])
 9646        });
 9647        editor.newline(&Newline, window, cx);
 9648        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 9649        assert_eq!(
 9650            editor.selections.ranges(&editor.display_snapshot(cx)),
 9651            &[
 9652                Point::new(1, 4)..Point::new(1, 4),
 9653                Point::new(3, 4)..Point::new(3, 4),
 9654                Point::new(5, 0)..Point::new(5, 0)
 9655            ]
 9656        );
 9657    });
 9658}
 9659
 9660#[gpui::test]
 9661async fn test_autoindent_disabled(cx: &mut TestAppContext) {
 9662    init_test(cx, |settings| settings.defaults.auto_indent = Some(false));
 9663
 9664    let language = Arc::new(
 9665        Language::new(
 9666            LanguageConfig {
 9667                brackets: BracketPairConfig {
 9668                    pairs: vec![
 9669                        BracketPair {
 9670                            start: "{".to_string(),
 9671                            end: "}".to_string(),
 9672                            close: false,
 9673                            surround: false,
 9674                            newline: true,
 9675                        },
 9676                        BracketPair {
 9677                            start: "(".to_string(),
 9678                            end: ")".to_string(),
 9679                            close: false,
 9680                            surround: false,
 9681                            newline: true,
 9682                        },
 9683                    ],
 9684                    ..Default::default()
 9685                },
 9686                ..Default::default()
 9687            },
 9688            Some(tree_sitter_rust::LANGUAGE.into()),
 9689        )
 9690        .with_indents_query(
 9691            r#"
 9692                (_ "(" ")" @end) @indent
 9693                (_ "{" "}" @end) @indent
 9694            "#,
 9695        )
 9696        .unwrap(),
 9697    );
 9698
 9699    let text = "fn a() {}";
 9700
 9701    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9702    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9703    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9704    editor
 9705        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9706        .await;
 9707
 9708    editor.update_in(cx, |editor, window, cx| {
 9709        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9710            s.select_ranges([5..5, 8..8, 9..9])
 9711        });
 9712        editor.newline(&Newline, window, cx);
 9713        assert_eq!(
 9714            editor.text(cx),
 9715            indoc!(
 9716                "
 9717                fn a(
 9718
 9719                ) {
 9720
 9721                }
 9722                "
 9723            )
 9724        );
 9725        assert_eq!(
 9726            editor.selections.ranges(&editor.display_snapshot(cx)),
 9727            &[
 9728                Point::new(1, 0)..Point::new(1, 0),
 9729                Point::new(3, 0)..Point::new(3, 0),
 9730                Point::new(5, 0)..Point::new(5, 0)
 9731            ]
 9732        );
 9733    });
 9734}
 9735
 9736#[gpui::test]
 9737async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
 9738    init_test(cx, |settings| {
 9739        settings.defaults.auto_indent = Some(true);
 9740        settings.languages.0.insert(
 9741            "python".into(),
 9742            LanguageSettingsContent {
 9743                auto_indent: Some(false),
 9744                ..Default::default()
 9745            },
 9746        );
 9747    });
 9748
 9749    let mut cx = EditorTestContext::new(cx).await;
 9750
 9751    let injected_language = Arc::new(
 9752        Language::new(
 9753            LanguageConfig {
 9754                brackets: BracketPairConfig {
 9755                    pairs: vec![
 9756                        BracketPair {
 9757                            start: "{".to_string(),
 9758                            end: "}".to_string(),
 9759                            close: false,
 9760                            surround: false,
 9761                            newline: true,
 9762                        },
 9763                        BracketPair {
 9764                            start: "(".to_string(),
 9765                            end: ")".to_string(),
 9766                            close: true,
 9767                            surround: false,
 9768                            newline: true,
 9769                        },
 9770                    ],
 9771                    ..Default::default()
 9772                },
 9773                name: "python".into(),
 9774                ..Default::default()
 9775            },
 9776            Some(tree_sitter_python::LANGUAGE.into()),
 9777        )
 9778        .with_indents_query(
 9779            r#"
 9780                (_ "(" ")" @end) @indent
 9781                (_ "{" "}" @end) @indent
 9782            "#,
 9783        )
 9784        .unwrap(),
 9785    );
 9786
 9787    let language = Arc::new(
 9788        Language::new(
 9789            LanguageConfig {
 9790                brackets: BracketPairConfig {
 9791                    pairs: vec![
 9792                        BracketPair {
 9793                            start: "{".to_string(),
 9794                            end: "}".to_string(),
 9795                            close: false,
 9796                            surround: false,
 9797                            newline: true,
 9798                        },
 9799                        BracketPair {
 9800                            start: "(".to_string(),
 9801                            end: ")".to_string(),
 9802                            close: true,
 9803                            surround: false,
 9804                            newline: true,
 9805                        },
 9806                    ],
 9807                    ..Default::default()
 9808                },
 9809                name: LanguageName::new("rust"),
 9810                ..Default::default()
 9811            },
 9812            Some(tree_sitter_rust::LANGUAGE.into()),
 9813        )
 9814        .with_indents_query(
 9815            r#"
 9816                (_ "(" ")" @end) @indent
 9817                (_ "{" "}" @end) @indent
 9818            "#,
 9819        )
 9820        .unwrap()
 9821        .with_injection_query(
 9822            r#"
 9823            (macro_invocation
 9824                macro: (identifier) @_macro_name
 9825                (token_tree) @injection.content
 9826                (#set! injection.language "python"))
 9827           "#,
 9828        )
 9829        .unwrap(),
 9830    );
 9831
 9832    cx.language_registry().add(injected_language);
 9833    cx.language_registry().add(language.clone());
 9834
 9835    cx.update_buffer(|buffer, cx| {
 9836        buffer.set_language(Some(language), cx);
 9837    });
 9838
 9839    cx.set_state(r#"struct A {ˇ}"#);
 9840
 9841    cx.update_editor(|editor, window, cx| {
 9842        editor.newline(&Default::default(), window, cx);
 9843    });
 9844
 9845    cx.assert_editor_state(indoc!(
 9846        "struct A {
 9847            ˇ
 9848        }"
 9849    ));
 9850
 9851    cx.set_state(r#"select_biased!(ˇ)"#);
 9852
 9853    cx.update_editor(|editor, window, cx| {
 9854        editor.newline(&Default::default(), window, cx);
 9855        editor.handle_input("def ", window, cx);
 9856        editor.handle_input("(", window, cx);
 9857        editor.newline(&Default::default(), window, cx);
 9858        editor.handle_input("a", window, cx);
 9859    });
 9860
 9861    cx.assert_editor_state(indoc!(
 9862        "select_biased!(
 9863        def (
 9864 9865        )
 9866        )"
 9867    ));
 9868}
 9869
 9870#[gpui::test]
 9871async fn test_autoindent_selections(cx: &mut TestAppContext) {
 9872    init_test(cx, |_| {});
 9873
 9874    {
 9875        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9876        cx.set_state(indoc! {"
 9877            impl A {
 9878
 9879                fn b() {}
 9880
 9881            «fn c() {
 9882
 9883            }ˇ»
 9884            }
 9885        "});
 9886
 9887        cx.update_editor(|editor, window, cx| {
 9888            editor.autoindent(&Default::default(), window, cx);
 9889        });
 9890
 9891        cx.assert_editor_state(indoc! {"
 9892            impl A {
 9893
 9894                fn b() {}
 9895
 9896                «fn c() {
 9897
 9898                }ˇ»
 9899            }
 9900        "});
 9901    }
 9902
 9903    {
 9904        let mut cx = EditorTestContext::new_multibuffer(
 9905            cx,
 9906            [indoc! { "
 9907                impl A {
 9908                «
 9909                // a
 9910                fn b(){}
 9911                »
 9912                «
 9913                    }
 9914                    fn c(){}
 9915                »
 9916            "}],
 9917        );
 9918
 9919        let buffer = cx.update_editor(|editor, _, cx| {
 9920            let buffer = editor.buffer().update(cx, |buffer, _| {
 9921                buffer.all_buffers().iter().next().unwrap().clone()
 9922            });
 9923            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 9924            buffer
 9925        });
 9926
 9927        cx.run_until_parked();
 9928        cx.update_editor(|editor, window, cx| {
 9929            editor.select_all(&Default::default(), window, cx);
 9930            editor.autoindent(&Default::default(), window, cx)
 9931        });
 9932        cx.run_until_parked();
 9933
 9934        cx.update(|_, cx| {
 9935            assert_eq!(
 9936                buffer.read(cx).text(),
 9937                indoc! { "
 9938                    impl A {
 9939
 9940                        // a
 9941                        fn b(){}
 9942
 9943
 9944                    }
 9945                    fn c(){}
 9946
 9947                " }
 9948            )
 9949        });
 9950    }
 9951}
 9952
 9953#[gpui::test]
 9954async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 9955    init_test(cx, |_| {});
 9956
 9957    let mut cx = EditorTestContext::new(cx).await;
 9958
 9959    let language = Arc::new(Language::new(
 9960        LanguageConfig {
 9961            brackets: BracketPairConfig {
 9962                pairs: vec![
 9963                    BracketPair {
 9964                        start: "{".to_string(),
 9965                        end: "}".to_string(),
 9966                        close: true,
 9967                        surround: true,
 9968                        newline: true,
 9969                    },
 9970                    BracketPair {
 9971                        start: "(".to_string(),
 9972                        end: ")".to_string(),
 9973                        close: true,
 9974                        surround: true,
 9975                        newline: true,
 9976                    },
 9977                    BracketPair {
 9978                        start: "/*".to_string(),
 9979                        end: " */".to_string(),
 9980                        close: true,
 9981                        surround: true,
 9982                        newline: true,
 9983                    },
 9984                    BracketPair {
 9985                        start: "[".to_string(),
 9986                        end: "]".to_string(),
 9987                        close: false,
 9988                        surround: false,
 9989                        newline: true,
 9990                    },
 9991                    BracketPair {
 9992                        start: "\"".to_string(),
 9993                        end: "\"".to_string(),
 9994                        close: true,
 9995                        surround: true,
 9996                        newline: false,
 9997                    },
 9998                    BracketPair {
 9999                        start: "<".to_string(),
10000                        end: ">".to_string(),
10001                        close: false,
10002                        surround: true,
10003                        newline: true,
10004                    },
10005                ],
10006                ..Default::default()
10007            },
10008            autoclose_before: "})]".to_string(),
10009            ..Default::default()
10010        },
10011        Some(tree_sitter_rust::LANGUAGE.into()),
10012    ));
10013
10014    cx.language_registry().add(language.clone());
10015    cx.update_buffer(|buffer, cx| {
10016        buffer.set_language(Some(language), cx);
10017    });
10018
10019    cx.set_state(
10020        &r#"
10021            🏀ˇ
10022            εˇ
10023            ❤️ˇ
10024        "#
10025        .unindent(),
10026    );
10027
10028    // autoclose multiple nested brackets at multiple cursors
10029    cx.update_editor(|editor, window, cx| {
10030        editor.handle_input("{", window, cx);
10031        editor.handle_input("{", window, cx);
10032        editor.handle_input("{", window, cx);
10033    });
10034    cx.assert_editor_state(
10035        &"
10036            🏀{{{ˇ}}}
10037            ε{{{ˇ}}}
10038            ❤️{{{ˇ}}}
10039        "
10040        .unindent(),
10041    );
10042
10043    // insert a different closing bracket
10044    cx.update_editor(|editor, window, cx| {
10045        editor.handle_input(")", window, cx);
10046    });
10047    cx.assert_editor_state(
10048        &"
10049            🏀{{{)ˇ}}}
10050            ε{{{)ˇ}}}
10051            ❤️{{{)ˇ}}}
10052        "
10053        .unindent(),
10054    );
10055
10056    // skip over the auto-closed brackets when typing a closing bracket
10057    cx.update_editor(|editor, window, cx| {
10058        editor.move_right(&MoveRight, window, cx);
10059        editor.handle_input("}", window, cx);
10060        editor.handle_input("}", window, cx);
10061        editor.handle_input("}", window, cx);
10062    });
10063    cx.assert_editor_state(
10064        &"
10065            🏀{{{)}}}}ˇ
10066            ε{{{)}}}}ˇ
10067            ❤️{{{)}}}}ˇ
10068        "
10069        .unindent(),
10070    );
10071
10072    // autoclose multi-character pairs
10073    cx.set_state(
10074        &"
10075            ˇ
10076            ˇ
10077        "
10078        .unindent(),
10079    );
10080    cx.update_editor(|editor, window, cx| {
10081        editor.handle_input("/", window, cx);
10082        editor.handle_input("*", window, cx);
10083    });
10084    cx.assert_editor_state(
10085        &"
10086            /*ˇ */
10087            /*ˇ */
10088        "
10089        .unindent(),
10090    );
10091
10092    // one cursor autocloses a multi-character pair, one cursor
10093    // does not autoclose.
10094    cx.set_state(
10095        &"
1009610097            ˇ
10098        "
10099        .unindent(),
10100    );
10101    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
10102    cx.assert_editor_state(
10103        &"
10104            /*ˇ */
1010510106        "
10107        .unindent(),
10108    );
10109
10110    // Don't autoclose if the next character isn't whitespace and isn't
10111    // listed in the language's "autoclose_before" section.
10112    cx.set_state("ˇa b");
10113    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10114    cx.assert_editor_state("{ˇa b");
10115
10116    // Don't autoclose if `close` is false for the bracket pair
10117    cx.set_state("ˇ");
10118    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
10119    cx.assert_editor_state("");
10120
10121    // Surround with brackets if text is selected
10122    cx.set_state("«aˇ» b");
10123    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10124    cx.assert_editor_state("{«aˇ»} b");
10125
10126    // Autoclose when not immediately after a word character
10127    cx.set_state("a ˇ");
10128    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10129    cx.assert_editor_state("a \"ˇ\"");
10130
10131    // Autoclose pair where the start and end characters are the same
10132    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10133    cx.assert_editor_state("a \"\"ˇ");
10134
10135    // Don't autoclose when immediately after a word character
10136    cx.set_state("");
10137    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10138    cx.assert_editor_state("a\"ˇ");
10139
10140    // Do autoclose when after a non-word character
10141    cx.set_state("");
10142    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10143    cx.assert_editor_state("{\"ˇ\"");
10144
10145    // Non identical pairs autoclose regardless of preceding character
10146    cx.set_state("");
10147    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10148    cx.assert_editor_state("a{ˇ}");
10149
10150    // Don't autoclose pair if autoclose is disabled
10151    cx.set_state("ˇ");
10152    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
10153    cx.assert_editor_state("");
10154
10155    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
10156    cx.set_state("«aˇ» b");
10157    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
10158    cx.assert_editor_state("<«aˇ»> b");
10159}
10160
10161#[gpui::test]
10162async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
10163    init_test(cx, |settings| {
10164        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10165    });
10166
10167    let mut cx = EditorTestContext::new(cx).await;
10168
10169    let language = Arc::new(Language::new(
10170        LanguageConfig {
10171            brackets: BracketPairConfig {
10172                pairs: vec![
10173                    BracketPair {
10174                        start: "{".to_string(),
10175                        end: "}".to_string(),
10176                        close: true,
10177                        surround: true,
10178                        newline: true,
10179                    },
10180                    BracketPair {
10181                        start: "(".to_string(),
10182                        end: ")".to_string(),
10183                        close: true,
10184                        surround: true,
10185                        newline: true,
10186                    },
10187                    BracketPair {
10188                        start: "[".to_string(),
10189                        end: "]".to_string(),
10190                        close: false,
10191                        surround: false,
10192                        newline: true,
10193                    },
10194                ],
10195                ..Default::default()
10196            },
10197            autoclose_before: "})]".to_string(),
10198            ..Default::default()
10199        },
10200        Some(tree_sitter_rust::LANGUAGE.into()),
10201    ));
10202
10203    cx.language_registry().add(language.clone());
10204    cx.update_buffer(|buffer, cx| {
10205        buffer.set_language(Some(language), cx);
10206    });
10207
10208    cx.set_state(
10209        &"
10210            ˇ
10211            ˇ
10212            ˇ
10213        "
10214        .unindent(),
10215    );
10216
10217    // ensure only matching closing brackets are skipped over
10218    cx.update_editor(|editor, window, cx| {
10219        editor.handle_input("}", window, cx);
10220        editor.move_left(&MoveLeft, window, cx);
10221        editor.handle_input(")", window, cx);
10222        editor.move_left(&MoveLeft, window, cx);
10223    });
10224    cx.assert_editor_state(
10225        &"
10226            ˇ)}
10227            ˇ)}
10228            ˇ)}
10229        "
10230        .unindent(),
10231    );
10232
10233    // skip-over closing brackets at multiple cursors
10234    cx.update_editor(|editor, window, cx| {
10235        editor.handle_input(")", window, cx);
10236        editor.handle_input("}", window, cx);
10237    });
10238    cx.assert_editor_state(
10239        &"
10240            )}ˇ
10241            )}ˇ
10242            )}ˇ
10243        "
10244        .unindent(),
10245    );
10246
10247    // ignore non-close brackets
10248    cx.update_editor(|editor, window, cx| {
10249        editor.handle_input("]", window, cx);
10250        editor.move_left(&MoveLeft, window, cx);
10251        editor.handle_input("]", window, cx);
10252    });
10253    cx.assert_editor_state(
10254        &"
10255            )}]ˇ]
10256            )}]ˇ]
10257            )}]ˇ]
10258        "
10259        .unindent(),
10260    );
10261}
10262
10263#[gpui::test]
10264async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
10265    init_test(cx, |_| {});
10266
10267    let mut cx = EditorTestContext::new(cx).await;
10268
10269    let html_language = Arc::new(
10270        Language::new(
10271            LanguageConfig {
10272                name: "HTML".into(),
10273                brackets: BracketPairConfig {
10274                    pairs: vec![
10275                        BracketPair {
10276                            start: "<".into(),
10277                            end: ">".into(),
10278                            close: true,
10279                            ..Default::default()
10280                        },
10281                        BracketPair {
10282                            start: "{".into(),
10283                            end: "}".into(),
10284                            close: true,
10285                            ..Default::default()
10286                        },
10287                        BracketPair {
10288                            start: "(".into(),
10289                            end: ")".into(),
10290                            close: true,
10291                            ..Default::default()
10292                        },
10293                    ],
10294                    ..Default::default()
10295                },
10296                autoclose_before: "})]>".into(),
10297                ..Default::default()
10298            },
10299            Some(tree_sitter_html::LANGUAGE.into()),
10300        )
10301        .with_injection_query(
10302            r#"
10303            (script_element
10304                (raw_text) @injection.content
10305                (#set! injection.language "javascript"))
10306            "#,
10307        )
10308        .unwrap(),
10309    );
10310
10311    let javascript_language = Arc::new(Language::new(
10312        LanguageConfig {
10313            name: "JavaScript".into(),
10314            brackets: BracketPairConfig {
10315                pairs: vec![
10316                    BracketPair {
10317                        start: "/*".into(),
10318                        end: " */".into(),
10319                        close: true,
10320                        ..Default::default()
10321                    },
10322                    BracketPair {
10323                        start: "{".into(),
10324                        end: "}".into(),
10325                        close: true,
10326                        ..Default::default()
10327                    },
10328                    BracketPair {
10329                        start: "(".into(),
10330                        end: ")".into(),
10331                        close: true,
10332                        ..Default::default()
10333                    },
10334                ],
10335                ..Default::default()
10336            },
10337            autoclose_before: "})]>".into(),
10338            ..Default::default()
10339        },
10340        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10341    ));
10342
10343    cx.language_registry().add(html_language.clone());
10344    cx.language_registry().add(javascript_language);
10345    cx.executor().run_until_parked();
10346
10347    cx.update_buffer(|buffer, cx| {
10348        buffer.set_language(Some(html_language), cx);
10349    });
10350
10351    cx.set_state(
10352        &r#"
10353            <body>ˇ
10354                <script>
10355                    var x = 1;ˇ
10356                </script>
10357            </body>ˇ
10358        "#
10359        .unindent(),
10360    );
10361
10362    // Precondition: different languages are active at different locations.
10363    cx.update_editor(|editor, window, cx| {
10364        let snapshot = editor.snapshot(window, cx);
10365        let cursors = editor
10366            .selections
10367            .ranges::<usize>(&editor.display_snapshot(cx));
10368        let languages = cursors
10369            .iter()
10370            .map(|c| snapshot.language_at(c.start).unwrap().name())
10371            .collect::<Vec<_>>();
10372        assert_eq!(
10373            languages,
10374            &["HTML".into(), "JavaScript".into(), "HTML".into()]
10375        );
10376    });
10377
10378    // Angle brackets autoclose in HTML, but not JavaScript.
10379    cx.update_editor(|editor, window, cx| {
10380        editor.handle_input("<", window, cx);
10381        editor.handle_input("a", window, cx);
10382    });
10383    cx.assert_editor_state(
10384        &r#"
10385            <body><aˇ>
10386                <script>
10387                    var x = 1;<aˇ
10388                </script>
10389            </body><aˇ>
10390        "#
10391        .unindent(),
10392    );
10393
10394    // Curly braces and parens autoclose in both HTML and JavaScript.
10395    cx.update_editor(|editor, window, cx| {
10396        editor.handle_input(" b=", window, cx);
10397        editor.handle_input("{", window, cx);
10398        editor.handle_input("c", window, cx);
10399        editor.handle_input("(", window, cx);
10400    });
10401    cx.assert_editor_state(
10402        &r#"
10403            <body><a b={c(ˇ)}>
10404                <script>
10405                    var x = 1;<a b={c(ˇ)}
10406                </script>
10407            </body><a b={c(ˇ)}>
10408        "#
10409        .unindent(),
10410    );
10411
10412    // Brackets that were already autoclosed are skipped.
10413    cx.update_editor(|editor, window, cx| {
10414        editor.handle_input(")", window, cx);
10415        editor.handle_input("d", window, cx);
10416        editor.handle_input("}", window, cx);
10417    });
10418    cx.assert_editor_state(
10419        &r#"
10420            <body><a b={c()d}ˇ>
10421                <script>
10422                    var x = 1;<a b={c()d}ˇ
10423                </script>
10424            </body><a b={c()d}ˇ>
10425        "#
10426        .unindent(),
10427    );
10428    cx.update_editor(|editor, window, cx| {
10429        editor.handle_input(">", window, cx);
10430    });
10431    cx.assert_editor_state(
10432        &r#"
10433            <body><a b={c()d}>ˇ
10434                <script>
10435                    var x = 1;<a b={c()d}>ˇ
10436                </script>
10437            </body><a b={c()d}>ˇ
10438        "#
10439        .unindent(),
10440    );
10441
10442    // Reset
10443    cx.set_state(
10444        &r#"
10445            <body>ˇ
10446                <script>
10447                    var x = 1;ˇ
10448                </script>
10449            </body>ˇ
10450        "#
10451        .unindent(),
10452    );
10453
10454    cx.update_editor(|editor, window, cx| {
10455        editor.handle_input("<", window, cx);
10456    });
10457    cx.assert_editor_state(
10458        &r#"
10459            <body><ˇ>
10460                <script>
10461                    var x = 1;<ˇ
10462                </script>
10463            </body><ˇ>
10464        "#
10465        .unindent(),
10466    );
10467
10468    // When backspacing, the closing angle brackets are removed.
10469    cx.update_editor(|editor, window, cx| {
10470        editor.backspace(&Backspace, window, cx);
10471    });
10472    cx.assert_editor_state(
10473        &r#"
10474            <body>ˇ
10475                <script>
10476                    var x = 1;ˇ
10477                </script>
10478            </body>ˇ
10479        "#
10480        .unindent(),
10481    );
10482
10483    // Block comments autoclose in JavaScript, but not HTML.
10484    cx.update_editor(|editor, window, cx| {
10485        editor.handle_input("/", window, cx);
10486        editor.handle_input("*", window, cx);
10487    });
10488    cx.assert_editor_state(
10489        &r#"
10490            <body>/*ˇ
10491                <script>
10492                    var x = 1;/*ˇ */
10493                </script>
10494            </body>/*ˇ
10495        "#
10496        .unindent(),
10497    );
10498}
10499
10500#[gpui::test]
10501async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
10502    init_test(cx, |_| {});
10503
10504    let mut cx = EditorTestContext::new(cx).await;
10505
10506    let rust_language = Arc::new(
10507        Language::new(
10508            LanguageConfig {
10509                name: "Rust".into(),
10510                brackets: serde_json::from_value(json!([
10511                    { "start": "{", "end": "}", "close": true, "newline": true },
10512                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
10513                ]))
10514                .unwrap(),
10515                autoclose_before: "})]>".into(),
10516                ..Default::default()
10517            },
10518            Some(tree_sitter_rust::LANGUAGE.into()),
10519        )
10520        .with_override_query("(string_literal) @string")
10521        .unwrap(),
10522    );
10523
10524    cx.language_registry().add(rust_language.clone());
10525    cx.update_buffer(|buffer, cx| {
10526        buffer.set_language(Some(rust_language), cx);
10527    });
10528
10529    cx.set_state(
10530        &r#"
10531            let x = ˇ
10532        "#
10533        .unindent(),
10534    );
10535
10536    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
10537    cx.update_editor(|editor, window, cx| {
10538        editor.handle_input("\"", window, cx);
10539    });
10540    cx.assert_editor_state(
10541        &r#"
10542            let x = "ˇ"
10543        "#
10544        .unindent(),
10545    );
10546
10547    // Inserting another quotation mark. The cursor moves across the existing
10548    // automatically-inserted quotation mark.
10549    cx.update_editor(|editor, window, cx| {
10550        editor.handle_input("\"", window, cx);
10551    });
10552    cx.assert_editor_state(
10553        &r#"
10554            let x = ""ˇ
10555        "#
10556        .unindent(),
10557    );
10558
10559    // Reset
10560    cx.set_state(
10561        &r#"
10562            let x = ˇ
10563        "#
10564        .unindent(),
10565    );
10566
10567    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
10568    cx.update_editor(|editor, window, cx| {
10569        editor.handle_input("\"", window, cx);
10570        editor.handle_input(" ", window, cx);
10571        editor.move_left(&Default::default(), window, cx);
10572        editor.handle_input("\\", window, cx);
10573        editor.handle_input("\"", window, cx);
10574    });
10575    cx.assert_editor_state(
10576        &r#"
10577            let x = "\"ˇ "
10578        "#
10579        .unindent(),
10580    );
10581
10582    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
10583    // mark. Nothing is inserted.
10584    cx.update_editor(|editor, window, cx| {
10585        editor.move_right(&Default::default(), window, cx);
10586        editor.handle_input("\"", window, cx);
10587    });
10588    cx.assert_editor_state(
10589        &r#"
10590            let x = "\" "ˇ
10591        "#
10592        .unindent(),
10593    );
10594}
10595
10596#[gpui::test]
10597async fn test_surround_with_pair(cx: &mut TestAppContext) {
10598    init_test(cx, |_| {});
10599
10600    let language = Arc::new(Language::new(
10601        LanguageConfig {
10602            brackets: BracketPairConfig {
10603                pairs: vec![
10604                    BracketPair {
10605                        start: "{".to_string(),
10606                        end: "}".to_string(),
10607                        close: true,
10608                        surround: true,
10609                        newline: true,
10610                    },
10611                    BracketPair {
10612                        start: "/* ".to_string(),
10613                        end: "*/".to_string(),
10614                        close: true,
10615                        surround: true,
10616                        ..Default::default()
10617                    },
10618                ],
10619                ..Default::default()
10620            },
10621            ..Default::default()
10622        },
10623        Some(tree_sitter_rust::LANGUAGE.into()),
10624    ));
10625
10626    let text = r#"
10627        a
10628        b
10629        c
10630    "#
10631    .unindent();
10632
10633    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10634    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10635    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10636    editor
10637        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10638        .await;
10639
10640    editor.update_in(cx, |editor, window, cx| {
10641        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10642            s.select_display_ranges([
10643                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10644                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10645                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
10646            ])
10647        });
10648
10649        editor.handle_input("{", window, cx);
10650        editor.handle_input("{", window, cx);
10651        editor.handle_input("{", window, cx);
10652        assert_eq!(
10653            editor.text(cx),
10654            "
10655                {{{a}}}
10656                {{{b}}}
10657                {{{c}}}
10658            "
10659            .unindent()
10660        );
10661        assert_eq!(
10662            display_ranges(editor, cx),
10663            [
10664                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
10665                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
10666                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
10667            ]
10668        );
10669
10670        editor.undo(&Undo, window, cx);
10671        editor.undo(&Undo, window, cx);
10672        editor.undo(&Undo, window, cx);
10673        assert_eq!(
10674            editor.text(cx),
10675            "
10676                a
10677                b
10678                c
10679            "
10680            .unindent()
10681        );
10682        assert_eq!(
10683            display_ranges(editor, cx),
10684            [
10685                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10686                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10687                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10688            ]
10689        );
10690
10691        // Ensure inserting the first character of a multi-byte bracket pair
10692        // doesn't surround the selections with the bracket.
10693        editor.handle_input("/", window, cx);
10694        assert_eq!(
10695            editor.text(cx),
10696            "
10697                /
10698                /
10699                /
10700            "
10701            .unindent()
10702        );
10703        assert_eq!(
10704            display_ranges(editor, cx),
10705            [
10706                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10707                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10708                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10709            ]
10710        );
10711
10712        editor.undo(&Undo, window, cx);
10713        assert_eq!(
10714            editor.text(cx),
10715            "
10716                a
10717                b
10718                c
10719            "
10720            .unindent()
10721        );
10722        assert_eq!(
10723            display_ranges(editor, cx),
10724            [
10725                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10726                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10727                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10728            ]
10729        );
10730
10731        // Ensure inserting the last character of a multi-byte bracket pair
10732        // doesn't surround the selections with the bracket.
10733        editor.handle_input("*", window, cx);
10734        assert_eq!(
10735            editor.text(cx),
10736            "
10737                *
10738                *
10739                *
10740            "
10741            .unindent()
10742        );
10743        assert_eq!(
10744            display_ranges(editor, cx),
10745            [
10746                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10747                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10748                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10749            ]
10750        );
10751    });
10752}
10753
10754#[gpui::test]
10755async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
10756    init_test(cx, |_| {});
10757
10758    let language = Arc::new(Language::new(
10759        LanguageConfig {
10760            brackets: BracketPairConfig {
10761                pairs: vec![BracketPair {
10762                    start: "{".to_string(),
10763                    end: "}".to_string(),
10764                    close: true,
10765                    surround: true,
10766                    newline: true,
10767                }],
10768                ..Default::default()
10769            },
10770            autoclose_before: "}".to_string(),
10771            ..Default::default()
10772        },
10773        Some(tree_sitter_rust::LANGUAGE.into()),
10774    ));
10775
10776    let text = r#"
10777        a
10778        b
10779        c
10780    "#
10781    .unindent();
10782
10783    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10784    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10785    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10786    editor
10787        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10788        .await;
10789
10790    editor.update_in(cx, |editor, window, cx| {
10791        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10792            s.select_ranges([
10793                Point::new(0, 1)..Point::new(0, 1),
10794                Point::new(1, 1)..Point::new(1, 1),
10795                Point::new(2, 1)..Point::new(2, 1),
10796            ])
10797        });
10798
10799        editor.handle_input("{", window, cx);
10800        editor.handle_input("{", window, cx);
10801        editor.handle_input("_", window, cx);
10802        assert_eq!(
10803            editor.text(cx),
10804            "
10805                a{{_}}
10806                b{{_}}
10807                c{{_}}
10808            "
10809            .unindent()
10810        );
10811        assert_eq!(
10812            editor
10813                .selections
10814                .ranges::<Point>(&editor.display_snapshot(cx)),
10815            [
10816                Point::new(0, 4)..Point::new(0, 4),
10817                Point::new(1, 4)..Point::new(1, 4),
10818                Point::new(2, 4)..Point::new(2, 4)
10819            ]
10820        );
10821
10822        editor.backspace(&Default::default(), window, cx);
10823        editor.backspace(&Default::default(), window, cx);
10824        assert_eq!(
10825            editor.text(cx),
10826            "
10827                a{}
10828                b{}
10829                c{}
10830            "
10831            .unindent()
10832        );
10833        assert_eq!(
10834            editor
10835                .selections
10836                .ranges::<Point>(&editor.display_snapshot(cx)),
10837            [
10838                Point::new(0, 2)..Point::new(0, 2),
10839                Point::new(1, 2)..Point::new(1, 2),
10840                Point::new(2, 2)..Point::new(2, 2)
10841            ]
10842        );
10843
10844        editor.delete_to_previous_word_start(&Default::default(), window, cx);
10845        assert_eq!(
10846            editor.text(cx),
10847            "
10848                a
10849                b
10850                c
10851            "
10852            .unindent()
10853        );
10854        assert_eq!(
10855            editor
10856                .selections
10857                .ranges::<Point>(&editor.display_snapshot(cx)),
10858            [
10859                Point::new(0, 1)..Point::new(0, 1),
10860                Point::new(1, 1)..Point::new(1, 1),
10861                Point::new(2, 1)..Point::new(2, 1)
10862            ]
10863        );
10864    });
10865}
10866
10867#[gpui::test]
10868async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
10869    init_test(cx, |settings| {
10870        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10871    });
10872
10873    let mut cx = EditorTestContext::new(cx).await;
10874
10875    let language = Arc::new(Language::new(
10876        LanguageConfig {
10877            brackets: BracketPairConfig {
10878                pairs: vec![
10879                    BracketPair {
10880                        start: "{".to_string(),
10881                        end: "}".to_string(),
10882                        close: true,
10883                        surround: true,
10884                        newline: true,
10885                    },
10886                    BracketPair {
10887                        start: "(".to_string(),
10888                        end: ")".to_string(),
10889                        close: true,
10890                        surround: true,
10891                        newline: true,
10892                    },
10893                    BracketPair {
10894                        start: "[".to_string(),
10895                        end: "]".to_string(),
10896                        close: false,
10897                        surround: true,
10898                        newline: true,
10899                    },
10900                ],
10901                ..Default::default()
10902            },
10903            autoclose_before: "})]".to_string(),
10904            ..Default::default()
10905        },
10906        Some(tree_sitter_rust::LANGUAGE.into()),
10907    ));
10908
10909    cx.language_registry().add(language.clone());
10910    cx.update_buffer(|buffer, cx| {
10911        buffer.set_language(Some(language), cx);
10912    });
10913
10914    cx.set_state(
10915        &"
10916            {(ˇ)}
10917            [[ˇ]]
10918            {(ˇ)}
10919        "
10920        .unindent(),
10921    );
10922
10923    cx.update_editor(|editor, window, cx| {
10924        editor.backspace(&Default::default(), window, cx);
10925        editor.backspace(&Default::default(), window, cx);
10926    });
10927
10928    cx.assert_editor_state(
10929        &"
10930            ˇ
10931            ˇ]]
10932            ˇ
10933        "
10934        .unindent(),
10935    );
10936
10937    cx.update_editor(|editor, window, cx| {
10938        editor.handle_input("{", window, cx);
10939        editor.handle_input("{", window, cx);
10940        editor.move_right(&MoveRight, window, cx);
10941        editor.move_right(&MoveRight, window, cx);
10942        editor.move_left(&MoveLeft, window, cx);
10943        editor.move_left(&MoveLeft, window, cx);
10944        editor.backspace(&Default::default(), window, cx);
10945    });
10946
10947    cx.assert_editor_state(
10948        &"
10949            {ˇ}
10950            {ˇ}]]
10951            {ˇ}
10952        "
10953        .unindent(),
10954    );
10955
10956    cx.update_editor(|editor, window, cx| {
10957        editor.backspace(&Default::default(), window, cx);
10958    });
10959
10960    cx.assert_editor_state(
10961        &"
10962            ˇ
10963            ˇ]]
10964            ˇ
10965        "
10966        .unindent(),
10967    );
10968}
10969
10970#[gpui::test]
10971async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
10972    init_test(cx, |_| {});
10973
10974    let language = Arc::new(Language::new(
10975        LanguageConfig::default(),
10976        Some(tree_sitter_rust::LANGUAGE.into()),
10977    ));
10978
10979    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
10980    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10981    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10982    editor
10983        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10984        .await;
10985
10986    editor.update_in(cx, |editor, window, cx| {
10987        editor.set_auto_replace_emoji_shortcode(true);
10988
10989        editor.handle_input("Hello ", window, cx);
10990        editor.handle_input(":wave", window, cx);
10991        assert_eq!(editor.text(cx), "Hello :wave".unindent());
10992
10993        editor.handle_input(":", window, cx);
10994        assert_eq!(editor.text(cx), "Hello 👋".unindent());
10995
10996        editor.handle_input(" :smile", window, cx);
10997        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
10998
10999        editor.handle_input(":", window, cx);
11000        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
11001
11002        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
11003        editor.handle_input(":wave", window, cx);
11004        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
11005
11006        editor.handle_input(":", window, cx);
11007        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
11008
11009        editor.handle_input(":1", window, cx);
11010        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
11011
11012        editor.handle_input(":", window, cx);
11013        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
11014
11015        // Ensure shortcode does not get replaced when it is part of a word
11016        editor.handle_input(" Test:wave", window, cx);
11017        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
11018
11019        editor.handle_input(":", window, cx);
11020        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
11021
11022        editor.set_auto_replace_emoji_shortcode(false);
11023
11024        // Ensure shortcode does not get replaced when auto replace is off
11025        editor.handle_input(" :wave", window, cx);
11026        assert_eq!(
11027            editor.text(cx),
11028            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
11029        );
11030
11031        editor.handle_input(":", window, cx);
11032        assert_eq!(
11033            editor.text(cx),
11034            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
11035        );
11036    });
11037}
11038
11039#[gpui::test]
11040async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
11041    init_test(cx, |_| {});
11042
11043    let (text, insertion_ranges) = marked_text_ranges(
11044        indoc! {"
11045            ˇ
11046        "},
11047        false,
11048    );
11049
11050    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
11051    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11052
11053    _ = editor.update_in(cx, |editor, window, cx| {
11054        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
11055
11056        editor
11057            .insert_snippet(&insertion_ranges, snippet, window, cx)
11058            .unwrap();
11059
11060        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
11061            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
11062            assert_eq!(editor.text(cx), expected_text);
11063            assert_eq!(
11064                editor
11065                    .selections
11066                    .ranges::<usize>(&editor.display_snapshot(cx)),
11067                selection_ranges
11068            );
11069        }
11070
11071        assert(
11072            editor,
11073            cx,
11074            indoc! {"
11075            type «» =•
11076            "},
11077        );
11078
11079        assert!(editor.context_menu_visible(), "There should be a matches");
11080    });
11081}
11082
11083#[gpui::test]
11084async fn test_snippets(cx: &mut TestAppContext) {
11085    init_test(cx, |_| {});
11086
11087    let mut cx = EditorTestContext::new(cx).await;
11088
11089    cx.set_state(indoc! {"
11090        a.ˇ b
11091        a.ˇ b
11092        a.ˇ b
11093    "});
11094
11095    cx.update_editor(|editor, window, cx| {
11096        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
11097        let insertion_ranges = editor
11098            .selections
11099            .all(&editor.display_snapshot(cx))
11100            .iter()
11101            .map(|s| s.range())
11102            .collect::<Vec<_>>();
11103        editor
11104            .insert_snippet(&insertion_ranges, snippet, window, cx)
11105            .unwrap();
11106    });
11107
11108    cx.assert_editor_state(indoc! {"
11109        a.f(«oneˇ», two, «threeˇ») b
11110        a.f(«oneˇ», two, «threeˇ») b
11111        a.f(«oneˇ», two, «threeˇ») b
11112    "});
11113
11114    // Can't move earlier than the first tab stop
11115    cx.update_editor(|editor, window, cx| {
11116        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
11117    });
11118    cx.assert_editor_state(indoc! {"
11119        a.f(«oneˇ», two, «threeˇ») b
11120        a.f(«oneˇ», two, «threeˇ») b
11121        a.f(«oneˇ», two, «threeˇ») b
11122    "});
11123
11124    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11125    cx.assert_editor_state(indoc! {"
11126        a.f(one, «twoˇ», three) b
11127        a.f(one, «twoˇ», three) b
11128        a.f(one, «twoˇ», three) b
11129    "});
11130
11131    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
11132    cx.assert_editor_state(indoc! {"
11133        a.f(«oneˇ», two, «threeˇ») b
11134        a.f(«oneˇ», two, «threeˇ») b
11135        a.f(«oneˇ», two, «threeˇ») b
11136    "});
11137
11138    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11139    cx.assert_editor_state(indoc! {"
11140        a.f(one, «twoˇ», three) b
11141        a.f(one, «twoˇ», three) b
11142        a.f(one, «twoˇ», three) b
11143    "});
11144    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11145    cx.assert_editor_state(indoc! {"
11146        a.f(one, two, three)ˇ b
11147        a.f(one, two, three)ˇ b
11148        a.f(one, two, three)ˇ b
11149    "});
11150
11151    // As soon as the last tab stop is reached, snippet state is gone
11152    cx.update_editor(|editor, window, cx| {
11153        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
11154    });
11155    cx.assert_editor_state(indoc! {"
11156        a.f(one, two, three)ˇ b
11157        a.f(one, two, three)ˇ b
11158        a.f(one, two, three)ˇ b
11159    "});
11160}
11161
11162#[gpui::test]
11163async fn test_snippet_indentation(cx: &mut TestAppContext) {
11164    init_test(cx, |_| {});
11165
11166    let mut cx = EditorTestContext::new(cx).await;
11167
11168    cx.update_editor(|editor, window, cx| {
11169        let snippet = Snippet::parse(indoc! {"
11170            /*
11171             * Multiline comment with leading indentation
11172             *
11173             * $1
11174             */
11175            $0"})
11176        .unwrap();
11177        let insertion_ranges = editor
11178            .selections
11179            .all(&editor.display_snapshot(cx))
11180            .iter()
11181            .map(|s| s.range())
11182            .collect::<Vec<_>>();
11183        editor
11184            .insert_snippet(&insertion_ranges, snippet, window, cx)
11185            .unwrap();
11186    });
11187
11188    cx.assert_editor_state(indoc! {"
11189        /*
11190         * Multiline comment with leading indentation
11191         *
11192         * ˇ
11193         */
11194    "});
11195
11196    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11197    cx.assert_editor_state(indoc! {"
11198        /*
11199         * Multiline comment with leading indentation
11200         *
11201         *•
11202         */
11203        ˇ"});
11204}
11205
11206#[gpui::test]
11207async fn test_document_format_during_save(cx: &mut TestAppContext) {
11208    init_test(cx, |_| {});
11209
11210    let fs = FakeFs::new(cx.executor());
11211    fs.insert_file(path!("/file.rs"), Default::default()).await;
11212
11213    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
11214
11215    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11216    language_registry.add(rust_lang());
11217    let mut fake_servers = language_registry.register_fake_lsp(
11218        "Rust",
11219        FakeLspAdapter {
11220            capabilities: lsp::ServerCapabilities {
11221                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11222                ..Default::default()
11223            },
11224            ..Default::default()
11225        },
11226    );
11227
11228    let buffer = project
11229        .update(cx, |project, cx| {
11230            project.open_local_buffer(path!("/file.rs"), cx)
11231        })
11232        .await
11233        .unwrap();
11234
11235    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11236    let (editor, cx) = cx.add_window_view(|window, cx| {
11237        build_editor_with_project(project.clone(), buffer, window, cx)
11238    });
11239    editor.update_in(cx, |editor, window, cx| {
11240        editor.set_text("one\ntwo\nthree\n", window, cx)
11241    });
11242    assert!(cx.read(|cx| editor.is_dirty(cx)));
11243
11244    cx.executor().start_waiting();
11245    let fake_server = fake_servers.next().await.unwrap();
11246
11247    {
11248        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11249            move |params, _| async move {
11250                assert_eq!(
11251                    params.text_document.uri,
11252                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11253                );
11254                assert_eq!(params.options.tab_size, 4);
11255                Ok(Some(vec![lsp::TextEdit::new(
11256                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11257                    ", ".to_string(),
11258                )]))
11259            },
11260        );
11261        let save = editor
11262            .update_in(cx, |editor, window, cx| {
11263                editor.save(
11264                    SaveOptions {
11265                        format: true,
11266                        autosave: false,
11267                    },
11268                    project.clone(),
11269                    window,
11270                    cx,
11271                )
11272            })
11273            .unwrap();
11274        cx.executor().start_waiting();
11275        save.await;
11276
11277        assert_eq!(
11278            editor.update(cx, |editor, cx| editor.text(cx)),
11279            "one, two\nthree\n"
11280        );
11281        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11282    }
11283
11284    {
11285        editor.update_in(cx, |editor, window, cx| {
11286            editor.set_text("one\ntwo\nthree\n", window, cx)
11287        });
11288        assert!(cx.read(|cx| editor.is_dirty(cx)));
11289
11290        // Ensure we can still save even if formatting hangs.
11291        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11292            move |params, _| async move {
11293                assert_eq!(
11294                    params.text_document.uri,
11295                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11296                );
11297                futures::future::pending::<()>().await;
11298                unreachable!()
11299            },
11300        );
11301        let save = editor
11302            .update_in(cx, |editor, window, cx| {
11303                editor.save(
11304                    SaveOptions {
11305                        format: true,
11306                        autosave: false,
11307                    },
11308                    project.clone(),
11309                    window,
11310                    cx,
11311                )
11312            })
11313            .unwrap();
11314        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11315        cx.executor().start_waiting();
11316        save.await;
11317        assert_eq!(
11318            editor.update(cx, |editor, cx| editor.text(cx)),
11319            "one\ntwo\nthree\n"
11320        );
11321    }
11322
11323    // Set rust language override and assert overridden tabsize is sent to language server
11324    update_test_language_settings(cx, |settings| {
11325        settings.languages.0.insert(
11326            "Rust".into(),
11327            LanguageSettingsContent {
11328                tab_size: NonZeroU32::new(8),
11329                ..Default::default()
11330            },
11331        );
11332    });
11333
11334    {
11335        editor.update_in(cx, |editor, window, cx| {
11336            editor.set_text("somehting_new\n", window, cx)
11337        });
11338        assert!(cx.read(|cx| editor.is_dirty(cx)));
11339        let _formatting_request_signal = fake_server
11340            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
11341                assert_eq!(
11342                    params.text_document.uri,
11343                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11344                );
11345                assert_eq!(params.options.tab_size, 8);
11346                Ok(Some(vec![]))
11347            });
11348        let save = editor
11349            .update_in(cx, |editor, window, cx| {
11350                editor.save(
11351                    SaveOptions {
11352                        format: true,
11353                        autosave: false,
11354                    },
11355                    project.clone(),
11356                    window,
11357                    cx,
11358                )
11359            })
11360            .unwrap();
11361        cx.executor().start_waiting();
11362        save.await;
11363    }
11364}
11365
11366#[gpui::test]
11367async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
11368    init_test(cx, |settings| {
11369        settings.defaults.ensure_final_newline_on_save = Some(false);
11370    });
11371
11372    let fs = FakeFs::new(cx.executor());
11373    fs.insert_file(path!("/file.txt"), "foo".into()).await;
11374
11375    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
11376
11377    let buffer = project
11378        .update(cx, |project, cx| {
11379            project.open_local_buffer(path!("/file.txt"), cx)
11380        })
11381        .await
11382        .unwrap();
11383
11384    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11385    let (editor, cx) = cx.add_window_view(|window, cx| {
11386        build_editor_with_project(project.clone(), buffer, window, cx)
11387    });
11388    editor.update_in(cx, |editor, window, cx| {
11389        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
11390            s.select_ranges([0..0])
11391        });
11392    });
11393    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11394
11395    editor.update_in(cx, |editor, window, cx| {
11396        editor.handle_input("\n", window, cx)
11397    });
11398    cx.run_until_parked();
11399    save(&editor, &project, cx).await;
11400    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11401
11402    editor.update_in(cx, |editor, window, cx| {
11403        editor.undo(&Default::default(), window, cx);
11404    });
11405    save(&editor, &project, cx).await;
11406    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11407
11408    editor.update_in(cx, |editor, window, cx| {
11409        editor.redo(&Default::default(), window, cx);
11410    });
11411    cx.run_until_parked();
11412    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11413
11414    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
11415        let save = editor
11416            .update_in(cx, |editor, window, cx| {
11417                editor.save(
11418                    SaveOptions {
11419                        format: true,
11420                        autosave: false,
11421                    },
11422                    project.clone(),
11423                    window,
11424                    cx,
11425                )
11426            })
11427            .unwrap();
11428        cx.executor().start_waiting();
11429        save.await;
11430        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11431    }
11432}
11433
11434#[gpui::test]
11435async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
11436    init_test(cx, |_| {});
11437
11438    let cols = 4;
11439    let rows = 10;
11440    let sample_text_1 = sample_text(rows, cols, 'a');
11441    assert_eq!(
11442        sample_text_1,
11443        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11444    );
11445    let sample_text_2 = sample_text(rows, cols, 'l');
11446    assert_eq!(
11447        sample_text_2,
11448        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11449    );
11450    let sample_text_3 = sample_text(rows, cols, 'v');
11451    assert_eq!(
11452        sample_text_3,
11453        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11454    );
11455
11456    let fs = FakeFs::new(cx.executor());
11457    fs.insert_tree(
11458        path!("/a"),
11459        json!({
11460            "main.rs": sample_text_1,
11461            "other.rs": sample_text_2,
11462            "lib.rs": sample_text_3,
11463        }),
11464    )
11465    .await;
11466
11467    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11468    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11469    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11470
11471    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11472    language_registry.add(rust_lang());
11473    let mut fake_servers = language_registry.register_fake_lsp(
11474        "Rust",
11475        FakeLspAdapter {
11476            capabilities: lsp::ServerCapabilities {
11477                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11478                ..Default::default()
11479            },
11480            ..Default::default()
11481        },
11482    );
11483
11484    let worktree = project.update(cx, |project, cx| {
11485        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
11486        assert_eq!(worktrees.len(), 1);
11487        worktrees.pop().unwrap()
11488    });
11489    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11490
11491    let buffer_1 = project
11492        .update(cx, |project, cx| {
11493            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
11494        })
11495        .await
11496        .unwrap();
11497    let buffer_2 = project
11498        .update(cx, |project, cx| {
11499            project.open_buffer((worktree_id, rel_path("other.rs")), cx)
11500        })
11501        .await
11502        .unwrap();
11503    let buffer_3 = project
11504        .update(cx, |project, cx| {
11505            project.open_buffer((worktree_id, rel_path("lib.rs")), cx)
11506        })
11507        .await
11508        .unwrap();
11509
11510    let multi_buffer = cx.new(|cx| {
11511        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11512        multi_buffer.push_excerpts(
11513            buffer_1.clone(),
11514            [
11515                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11516                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11517                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11518            ],
11519            cx,
11520        );
11521        multi_buffer.push_excerpts(
11522            buffer_2.clone(),
11523            [
11524                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11525                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11526                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11527            ],
11528            cx,
11529        );
11530        multi_buffer.push_excerpts(
11531            buffer_3.clone(),
11532            [
11533                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11534                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11535                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11536            ],
11537            cx,
11538        );
11539        multi_buffer
11540    });
11541    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
11542        Editor::new(
11543            EditorMode::full(),
11544            multi_buffer,
11545            Some(project.clone()),
11546            window,
11547            cx,
11548        )
11549    });
11550
11551    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11552        editor.change_selections(
11553            SelectionEffects::scroll(Autoscroll::Next),
11554            window,
11555            cx,
11556            |s| s.select_ranges(Some(1..2)),
11557        );
11558        editor.insert("|one|two|three|", window, cx);
11559    });
11560    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11561    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11562        editor.change_selections(
11563            SelectionEffects::scroll(Autoscroll::Next),
11564            window,
11565            cx,
11566            |s| s.select_ranges(Some(60..70)),
11567        );
11568        editor.insert("|four|five|six|", window, cx);
11569    });
11570    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11571
11572    // First two buffers should be edited, but not the third one.
11573    assert_eq!(
11574        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11575        "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}",
11576    );
11577    buffer_1.update(cx, |buffer, _| {
11578        assert!(buffer.is_dirty());
11579        assert_eq!(
11580            buffer.text(),
11581            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
11582        )
11583    });
11584    buffer_2.update(cx, |buffer, _| {
11585        assert!(buffer.is_dirty());
11586        assert_eq!(
11587            buffer.text(),
11588            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
11589        )
11590    });
11591    buffer_3.update(cx, |buffer, _| {
11592        assert!(!buffer.is_dirty());
11593        assert_eq!(buffer.text(), sample_text_3,)
11594    });
11595    cx.executor().run_until_parked();
11596
11597    cx.executor().start_waiting();
11598    let save = multi_buffer_editor
11599        .update_in(cx, |editor, window, cx| {
11600            editor.save(
11601                SaveOptions {
11602                    format: true,
11603                    autosave: false,
11604                },
11605                project.clone(),
11606                window,
11607                cx,
11608            )
11609        })
11610        .unwrap();
11611
11612    let fake_server = fake_servers.next().await.unwrap();
11613    fake_server
11614        .server
11615        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
11616            Ok(Some(vec![lsp::TextEdit::new(
11617                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11618                format!("[{} formatted]", params.text_document.uri),
11619            )]))
11620        })
11621        .detach();
11622    save.await;
11623
11624    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
11625    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
11626    assert_eq!(
11627        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11628        uri!(
11629            "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}"
11630        ),
11631    );
11632    buffer_1.update(cx, |buffer, _| {
11633        assert!(!buffer.is_dirty());
11634        assert_eq!(
11635            buffer.text(),
11636            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
11637        )
11638    });
11639    buffer_2.update(cx, |buffer, _| {
11640        assert!(!buffer.is_dirty());
11641        assert_eq!(
11642            buffer.text(),
11643            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
11644        )
11645    });
11646    buffer_3.update(cx, |buffer, _| {
11647        assert!(!buffer.is_dirty());
11648        assert_eq!(buffer.text(), sample_text_3,)
11649    });
11650}
11651
11652#[gpui::test]
11653async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
11654    init_test(cx, |_| {});
11655
11656    let fs = FakeFs::new(cx.executor());
11657    fs.insert_tree(
11658        path!("/dir"),
11659        json!({
11660            "file1.rs": "fn main() { println!(\"hello\"); }",
11661            "file2.rs": "fn test() { println!(\"test\"); }",
11662            "file3.rs": "fn other() { println!(\"other\"); }\n",
11663        }),
11664    )
11665    .await;
11666
11667    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
11668    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11669    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11670
11671    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11672    language_registry.add(rust_lang());
11673
11674    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
11675    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11676
11677    // Open three buffers
11678    let buffer_1 = project
11679        .update(cx, |project, cx| {
11680            project.open_buffer((worktree_id, rel_path("file1.rs")), cx)
11681        })
11682        .await
11683        .unwrap();
11684    let buffer_2 = project
11685        .update(cx, |project, cx| {
11686            project.open_buffer((worktree_id, rel_path("file2.rs")), cx)
11687        })
11688        .await
11689        .unwrap();
11690    let buffer_3 = project
11691        .update(cx, |project, cx| {
11692            project.open_buffer((worktree_id, rel_path("file3.rs")), cx)
11693        })
11694        .await
11695        .unwrap();
11696
11697    // Create a multi-buffer with all three buffers
11698    let multi_buffer = cx.new(|cx| {
11699        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11700        multi_buffer.push_excerpts(
11701            buffer_1.clone(),
11702            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11703            cx,
11704        );
11705        multi_buffer.push_excerpts(
11706            buffer_2.clone(),
11707            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11708            cx,
11709        );
11710        multi_buffer.push_excerpts(
11711            buffer_3.clone(),
11712            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11713            cx,
11714        );
11715        multi_buffer
11716    });
11717
11718    let editor = cx.new_window_entity(|window, cx| {
11719        Editor::new(
11720            EditorMode::full(),
11721            multi_buffer,
11722            Some(project.clone()),
11723            window,
11724            cx,
11725        )
11726    });
11727
11728    // Edit only the first buffer
11729    editor.update_in(cx, |editor, window, cx| {
11730        editor.change_selections(
11731            SelectionEffects::scroll(Autoscroll::Next),
11732            window,
11733            cx,
11734            |s| s.select_ranges(Some(10..10)),
11735        );
11736        editor.insert("// edited", window, cx);
11737    });
11738
11739    // Verify that only buffer 1 is dirty
11740    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
11741    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11742    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11743
11744    // Get write counts after file creation (files were created with initial content)
11745    // We expect each file to have been written once during creation
11746    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
11747    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
11748    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
11749
11750    // Perform autosave
11751    let save_task = editor.update_in(cx, |editor, window, cx| {
11752        editor.save(
11753            SaveOptions {
11754                format: true,
11755                autosave: true,
11756            },
11757            project.clone(),
11758            window,
11759            cx,
11760        )
11761    });
11762    save_task.await.unwrap();
11763
11764    // Only the dirty buffer should have been saved
11765    assert_eq!(
11766        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11767        1,
11768        "Buffer 1 was dirty, so it should have been written once during autosave"
11769    );
11770    assert_eq!(
11771        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11772        0,
11773        "Buffer 2 was clean, so it should not have been written during autosave"
11774    );
11775    assert_eq!(
11776        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11777        0,
11778        "Buffer 3 was clean, so it should not have been written during autosave"
11779    );
11780
11781    // Verify buffer states after autosave
11782    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11783    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11784    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11785
11786    // Now perform a manual save (format = true)
11787    let save_task = editor.update_in(cx, |editor, window, cx| {
11788        editor.save(
11789            SaveOptions {
11790                format: true,
11791                autosave: false,
11792            },
11793            project.clone(),
11794            window,
11795            cx,
11796        )
11797    });
11798    save_task.await.unwrap();
11799
11800    // During manual save, clean buffers don't get written to disk
11801    // They just get did_save called for language server notifications
11802    assert_eq!(
11803        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11804        1,
11805        "Buffer 1 should only have been written once total (during autosave, not manual save)"
11806    );
11807    assert_eq!(
11808        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11809        0,
11810        "Buffer 2 should not have been written at all"
11811    );
11812    assert_eq!(
11813        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11814        0,
11815        "Buffer 3 should not have been written at all"
11816    );
11817}
11818
11819async fn setup_range_format_test(
11820    cx: &mut TestAppContext,
11821) -> (
11822    Entity<Project>,
11823    Entity<Editor>,
11824    &mut gpui::VisualTestContext,
11825    lsp::FakeLanguageServer,
11826) {
11827    init_test(cx, |_| {});
11828
11829    let fs = FakeFs::new(cx.executor());
11830    fs.insert_file(path!("/file.rs"), Default::default()).await;
11831
11832    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11833
11834    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11835    language_registry.add(rust_lang());
11836    let mut fake_servers = language_registry.register_fake_lsp(
11837        "Rust",
11838        FakeLspAdapter {
11839            capabilities: lsp::ServerCapabilities {
11840                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
11841                ..lsp::ServerCapabilities::default()
11842            },
11843            ..FakeLspAdapter::default()
11844        },
11845    );
11846
11847    let buffer = project
11848        .update(cx, |project, cx| {
11849            project.open_local_buffer(path!("/file.rs"), cx)
11850        })
11851        .await
11852        .unwrap();
11853
11854    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11855    let (editor, cx) = cx.add_window_view(|window, cx| {
11856        build_editor_with_project(project.clone(), buffer, window, cx)
11857    });
11858
11859    cx.executor().start_waiting();
11860    let fake_server = fake_servers.next().await.unwrap();
11861
11862    (project, editor, cx, fake_server)
11863}
11864
11865#[gpui::test]
11866async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
11867    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11868
11869    editor.update_in(cx, |editor, window, cx| {
11870        editor.set_text("one\ntwo\nthree\n", window, cx)
11871    });
11872    assert!(cx.read(|cx| editor.is_dirty(cx)));
11873
11874    let save = editor
11875        .update_in(cx, |editor, window, cx| {
11876            editor.save(
11877                SaveOptions {
11878                    format: true,
11879                    autosave: false,
11880                },
11881                project.clone(),
11882                window,
11883                cx,
11884            )
11885        })
11886        .unwrap();
11887    fake_server
11888        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
11889            assert_eq!(
11890                params.text_document.uri,
11891                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11892            );
11893            assert_eq!(params.options.tab_size, 4);
11894            Ok(Some(vec![lsp::TextEdit::new(
11895                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11896                ", ".to_string(),
11897            )]))
11898        })
11899        .next()
11900        .await;
11901    cx.executor().start_waiting();
11902    save.await;
11903    assert_eq!(
11904        editor.update(cx, |editor, cx| editor.text(cx)),
11905        "one, two\nthree\n"
11906    );
11907    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11908}
11909
11910#[gpui::test]
11911async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
11912    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11913
11914    editor.update_in(cx, |editor, window, cx| {
11915        editor.set_text("one\ntwo\nthree\n", window, cx)
11916    });
11917    assert!(cx.read(|cx| editor.is_dirty(cx)));
11918
11919    // Test that save still works when formatting hangs
11920    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
11921        move |params, _| async move {
11922            assert_eq!(
11923                params.text_document.uri,
11924                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11925            );
11926            futures::future::pending::<()>().await;
11927            unreachable!()
11928        },
11929    );
11930    let save = editor
11931        .update_in(cx, |editor, window, cx| {
11932            editor.save(
11933                SaveOptions {
11934                    format: true,
11935                    autosave: false,
11936                },
11937                project.clone(),
11938                window,
11939                cx,
11940            )
11941        })
11942        .unwrap();
11943    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11944    cx.executor().start_waiting();
11945    save.await;
11946    assert_eq!(
11947        editor.update(cx, |editor, cx| editor.text(cx)),
11948        "one\ntwo\nthree\n"
11949    );
11950    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11951}
11952
11953#[gpui::test]
11954async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
11955    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11956
11957    // Buffer starts clean, no formatting should be requested
11958    let save = editor
11959        .update_in(cx, |editor, window, cx| {
11960            editor.save(
11961                SaveOptions {
11962                    format: false,
11963                    autosave: false,
11964                },
11965                project.clone(),
11966                window,
11967                cx,
11968            )
11969        })
11970        .unwrap();
11971    let _pending_format_request = fake_server
11972        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
11973            panic!("Should not be invoked");
11974        })
11975        .next();
11976    cx.executor().start_waiting();
11977    save.await;
11978    cx.run_until_parked();
11979}
11980
11981#[gpui::test]
11982async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
11983    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11984
11985    // Set Rust language override and assert overridden tabsize is sent to language server
11986    update_test_language_settings(cx, |settings| {
11987        settings.languages.0.insert(
11988            "Rust".into(),
11989            LanguageSettingsContent {
11990                tab_size: NonZeroU32::new(8),
11991                ..Default::default()
11992            },
11993        );
11994    });
11995
11996    editor.update_in(cx, |editor, window, cx| {
11997        editor.set_text("something_new\n", window, cx)
11998    });
11999    assert!(cx.read(|cx| editor.is_dirty(cx)));
12000    let save = editor
12001        .update_in(cx, |editor, window, cx| {
12002            editor.save(
12003                SaveOptions {
12004                    format: true,
12005                    autosave: false,
12006                },
12007                project.clone(),
12008                window,
12009                cx,
12010            )
12011        })
12012        .unwrap();
12013    fake_server
12014        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
12015            assert_eq!(
12016                params.text_document.uri,
12017                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12018            );
12019            assert_eq!(params.options.tab_size, 8);
12020            Ok(Some(Vec::new()))
12021        })
12022        .next()
12023        .await;
12024    save.await;
12025}
12026
12027#[gpui::test]
12028async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
12029    init_test(cx, |settings| {
12030        settings.defaults.formatter = Some(FormatterList::Single(Formatter::LanguageServer(
12031            settings::LanguageServerFormatterSpecifier::Current,
12032        )))
12033    });
12034
12035    let fs = FakeFs::new(cx.executor());
12036    fs.insert_file(path!("/file.rs"), Default::default()).await;
12037
12038    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12039
12040    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12041    language_registry.add(Arc::new(Language::new(
12042        LanguageConfig {
12043            name: "Rust".into(),
12044            matcher: LanguageMatcher {
12045                path_suffixes: vec!["rs".to_string()],
12046                ..Default::default()
12047            },
12048            ..LanguageConfig::default()
12049        },
12050        Some(tree_sitter_rust::LANGUAGE.into()),
12051    )));
12052    update_test_language_settings(cx, |settings| {
12053        // Enable Prettier formatting for the same buffer, and ensure
12054        // LSP is called instead of Prettier.
12055        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12056    });
12057    let mut fake_servers = language_registry.register_fake_lsp(
12058        "Rust",
12059        FakeLspAdapter {
12060            capabilities: lsp::ServerCapabilities {
12061                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12062                ..Default::default()
12063            },
12064            ..Default::default()
12065        },
12066    );
12067
12068    let buffer = project
12069        .update(cx, |project, cx| {
12070            project.open_local_buffer(path!("/file.rs"), cx)
12071        })
12072        .await
12073        .unwrap();
12074
12075    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12076    let (editor, cx) = cx.add_window_view(|window, cx| {
12077        build_editor_with_project(project.clone(), buffer, window, cx)
12078    });
12079    editor.update_in(cx, |editor, window, cx| {
12080        editor.set_text("one\ntwo\nthree\n", window, cx)
12081    });
12082
12083    cx.executor().start_waiting();
12084    let fake_server = fake_servers.next().await.unwrap();
12085
12086    let format = editor
12087        .update_in(cx, |editor, window, cx| {
12088            editor.perform_format(
12089                project.clone(),
12090                FormatTrigger::Manual,
12091                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12092                window,
12093                cx,
12094            )
12095        })
12096        .unwrap();
12097    fake_server
12098        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
12099            assert_eq!(
12100                params.text_document.uri,
12101                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12102            );
12103            assert_eq!(params.options.tab_size, 4);
12104            Ok(Some(vec![lsp::TextEdit::new(
12105                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12106                ", ".to_string(),
12107            )]))
12108        })
12109        .next()
12110        .await;
12111    cx.executor().start_waiting();
12112    format.await;
12113    assert_eq!(
12114        editor.update(cx, |editor, cx| editor.text(cx)),
12115        "one, two\nthree\n"
12116    );
12117
12118    editor.update_in(cx, |editor, window, cx| {
12119        editor.set_text("one\ntwo\nthree\n", window, cx)
12120    });
12121    // Ensure we don't lock if formatting hangs.
12122    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12123        move |params, _| async move {
12124            assert_eq!(
12125                params.text_document.uri,
12126                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12127            );
12128            futures::future::pending::<()>().await;
12129            unreachable!()
12130        },
12131    );
12132    let format = editor
12133        .update_in(cx, |editor, window, cx| {
12134            editor.perform_format(
12135                project,
12136                FormatTrigger::Manual,
12137                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12138                window,
12139                cx,
12140            )
12141        })
12142        .unwrap();
12143    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12144    cx.executor().start_waiting();
12145    format.await;
12146    assert_eq!(
12147        editor.update(cx, |editor, cx| editor.text(cx)),
12148        "one\ntwo\nthree\n"
12149    );
12150}
12151
12152#[gpui::test]
12153async fn test_multiple_formatters(cx: &mut TestAppContext) {
12154    init_test(cx, |settings| {
12155        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
12156        settings.defaults.formatter = Some(FormatterList::Vec(vec![
12157            Formatter::LanguageServer(settings::LanguageServerFormatterSpecifier::Current),
12158            Formatter::CodeAction("code-action-1".into()),
12159            Formatter::CodeAction("code-action-2".into()),
12160        ]))
12161    });
12162
12163    let fs = FakeFs::new(cx.executor());
12164    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
12165        .await;
12166
12167    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12168    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12169    language_registry.add(rust_lang());
12170
12171    let mut fake_servers = language_registry.register_fake_lsp(
12172        "Rust",
12173        FakeLspAdapter {
12174            capabilities: lsp::ServerCapabilities {
12175                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12176                execute_command_provider: Some(lsp::ExecuteCommandOptions {
12177                    commands: vec!["the-command-for-code-action-1".into()],
12178                    ..Default::default()
12179                }),
12180                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12181                ..Default::default()
12182            },
12183            ..Default::default()
12184        },
12185    );
12186
12187    let buffer = project
12188        .update(cx, |project, cx| {
12189            project.open_local_buffer(path!("/file.rs"), cx)
12190        })
12191        .await
12192        .unwrap();
12193
12194    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12195    let (editor, cx) = cx.add_window_view(|window, cx| {
12196        build_editor_with_project(project.clone(), buffer, window, cx)
12197    });
12198
12199    cx.executor().start_waiting();
12200
12201    let fake_server = fake_servers.next().await.unwrap();
12202    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12203        move |_params, _| async move {
12204            Ok(Some(vec![lsp::TextEdit::new(
12205                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12206                "applied-formatting\n".to_string(),
12207            )]))
12208        },
12209    );
12210    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12211        move |params, _| async move {
12212            let requested_code_actions = params.context.only.expect("Expected code action request");
12213            assert_eq!(requested_code_actions.len(), 1);
12214
12215            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
12216            let code_action = match requested_code_actions[0].as_str() {
12217                "code-action-1" => lsp::CodeAction {
12218                    kind: Some("code-action-1".into()),
12219                    edit: Some(lsp::WorkspaceEdit::new(
12220                        [(
12221                            uri,
12222                            vec![lsp::TextEdit::new(
12223                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12224                                "applied-code-action-1-edit\n".to_string(),
12225                            )],
12226                        )]
12227                        .into_iter()
12228                        .collect(),
12229                    )),
12230                    command: Some(lsp::Command {
12231                        command: "the-command-for-code-action-1".into(),
12232                        ..Default::default()
12233                    }),
12234                    ..Default::default()
12235                },
12236                "code-action-2" => lsp::CodeAction {
12237                    kind: Some("code-action-2".into()),
12238                    edit: Some(lsp::WorkspaceEdit::new(
12239                        [(
12240                            uri,
12241                            vec![lsp::TextEdit::new(
12242                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12243                                "applied-code-action-2-edit\n".to_string(),
12244                            )],
12245                        )]
12246                        .into_iter()
12247                        .collect(),
12248                    )),
12249                    ..Default::default()
12250                },
12251                req => panic!("Unexpected code action request: {:?}", req),
12252            };
12253            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12254                code_action,
12255            )]))
12256        },
12257    );
12258
12259    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
12260        move |params, _| async move { Ok(params) }
12261    });
12262
12263    let command_lock = Arc::new(futures::lock::Mutex::new(()));
12264    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
12265        let fake = fake_server.clone();
12266        let lock = command_lock.clone();
12267        move |params, _| {
12268            assert_eq!(params.command, "the-command-for-code-action-1");
12269            let fake = fake.clone();
12270            let lock = lock.clone();
12271            async move {
12272                lock.lock().await;
12273                fake.server
12274                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
12275                        label: None,
12276                        edit: lsp::WorkspaceEdit {
12277                            changes: Some(
12278                                [(
12279                                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
12280                                    vec![lsp::TextEdit {
12281                                        range: lsp::Range::new(
12282                                            lsp::Position::new(0, 0),
12283                                            lsp::Position::new(0, 0),
12284                                        ),
12285                                        new_text: "applied-code-action-1-command\n".into(),
12286                                    }],
12287                                )]
12288                                .into_iter()
12289                                .collect(),
12290                            ),
12291                            ..Default::default()
12292                        },
12293                    })
12294                    .await
12295                    .into_response()
12296                    .unwrap();
12297                Ok(Some(json!(null)))
12298            }
12299        }
12300    });
12301
12302    cx.executor().start_waiting();
12303    editor
12304        .update_in(cx, |editor, window, cx| {
12305            editor.perform_format(
12306                project.clone(),
12307                FormatTrigger::Manual,
12308                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12309                window,
12310                cx,
12311            )
12312        })
12313        .unwrap()
12314        .await;
12315    editor.update(cx, |editor, cx| {
12316        assert_eq!(
12317            editor.text(cx),
12318            r#"
12319                applied-code-action-2-edit
12320                applied-code-action-1-command
12321                applied-code-action-1-edit
12322                applied-formatting
12323                one
12324                two
12325                three
12326            "#
12327            .unindent()
12328        );
12329    });
12330
12331    editor.update_in(cx, |editor, window, cx| {
12332        editor.undo(&Default::default(), window, cx);
12333        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12334    });
12335
12336    // Perform a manual edit while waiting for an LSP command
12337    // that's being run as part of a formatting code action.
12338    let lock_guard = command_lock.lock().await;
12339    let format = editor
12340        .update_in(cx, |editor, window, cx| {
12341            editor.perform_format(
12342                project.clone(),
12343                FormatTrigger::Manual,
12344                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12345                window,
12346                cx,
12347            )
12348        })
12349        .unwrap();
12350    cx.run_until_parked();
12351    editor.update(cx, |editor, cx| {
12352        assert_eq!(
12353            editor.text(cx),
12354            r#"
12355                applied-code-action-1-edit
12356                applied-formatting
12357                one
12358                two
12359                three
12360            "#
12361            .unindent()
12362        );
12363
12364        editor.buffer.update(cx, |buffer, cx| {
12365            let ix = buffer.len(cx);
12366            buffer.edit([(ix..ix, "edited\n")], None, cx);
12367        });
12368    });
12369
12370    // Allow the LSP command to proceed. Because the buffer was edited,
12371    // the second code action will not be run.
12372    drop(lock_guard);
12373    format.await;
12374    editor.update_in(cx, |editor, window, cx| {
12375        assert_eq!(
12376            editor.text(cx),
12377            r#"
12378                applied-code-action-1-command
12379                applied-code-action-1-edit
12380                applied-formatting
12381                one
12382                two
12383                three
12384                edited
12385            "#
12386            .unindent()
12387        );
12388
12389        // The manual edit is undone first, because it is the last thing the user did
12390        // (even though the command completed afterwards).
12391        editor.undo(&Default::default(), window, cx);
12392        assert_eq!(
12393            editor.text(cx),
12394            r#"
12395                applied-code-action-1-command
12396                applied-code-action-1-edit
12397                applied-formatting
12398                one
12399                two
12400                three
12401            "#
12402            .unindent()
12403        );
12404
12405        // All the formatting (including the command, which completed after the manual edit)
12406        // is undone together.
12407        editor.undo(&Default::default(), window, cx);
12408        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12409    });
12410}
12411
12412#[gpui::test]
12413async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
12414    init_test(cx, |settings| {
12415        settings.defaults.formatter = Some(FormatterList::Vec(vec![Formatter::LanguageServer(
12416            settings::LanguageServerFormatterSpecifier::Current,
12417        )]))
12418    });
12419
12420    let fs = FakeFs::new(cx.executor());
12421    fs.insert_file(path!("/file.ts"), Default::default()).await;
12422
12423    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12424
12425    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12426    language_registry.add(Arc::new(Language::new(
12427        LanguageConfig {
12428            name: "TypeScript".into(),
12429            matcher: LanguageMatcher {
12430                path_suffixes: vec!["ts".to_string()],
12431                ..Default::default()
12432            },
12433            ..LanguageConfig::default()
12434        },
12435        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12436    )));
12437    update_test_language_settings(cx, |settings| {
12438        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12439    });
12440    let mut fake_servers = language_registry.register_fake_lsp(
12441        "TypeScript",
12442        FakeLspAdapter {
12443            capabilities: lsp::ServerCapabilities {
12444                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12445                ..Default::default()
12446            },
12447            ..Default::default()
12448        },
12449    );
12450
12451    let buffer = project
12452        .update(cx, |project, cx| {
12453            project.open_local_buffer(path!("/file.ts"), cx)
12454        })
12455        .await
12456        .unwrap();
12457
12458    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12459    let (editor, cx) = cx.add_window_view(|window, cx| {
12460        build_editor_with_project(project.clone(), buffer, window, cx)
12461    });
12462    editor.update_in(cx, |editor, window, cx| {
12463        editor.set_text(
12464            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12465            window,
12466            cx,
12467        )
12468    });
12469
12470    cx.executor().start_waiting();
12471    let fake_server = fake_servers.next().await.unwrap();
12472
12473    let format = editor
12474        .update_in(cx, |editor, window, cx| {
12475            editor.perform_code_action_kind(
12476                project.clone(),
12477                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12478                window,
12479                cx,
12480            )
12481        })
12482        .unwrap();
12483    fake_server
12484        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
12485            assert_eq!(
12486                params.text_document.uri,
12487                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12488            );
12489            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12490                lsp::CodeAction {
12491                    title: "Organize Imports".to_string(),
12492                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
12493                    edit: Some(lsp::WorkspaceEdit {
12494                        changes: Some(
12495                            [(
12496                                params.text_document.uri.clone(),
12497                                vec![lsp::TextEdit::new(
12498                                    lsp::Range::new(
12499                                        lsp::Position::new(1, 0),
12500                                        lsp::Position::new(2, 0),
12501                                    ),
12502                                    "".to_string(),
12503                                )],
12504                            )]
12505                            .into_iter()
12506                            .collect(),
12507                        ),
12508                        ..Default::default()
12509                    }),
12510                    ..Default::default()
12511                },
12512            )]))
12513        })
12514        .next()
12515        .await;
12516    cx.executor().start_waiting();
12517    format.await;
12518    assert_eq!(
12519        editor.update(cx, |editor, cx| editor.text(cx)),
12520        "import { a } from 'module';\n\nconst x = a;\n"
12521    );
12522
12523    editor.update_in(cx, |editor, window, cx| {
12524        editor.set_text(
12525            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12526            window,
12527            cx,
12528        )
12529    });
12530    // Ensure we don't lock if code action hangs.
12531    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12532        move |params, _| async move {
12533            assert_eq!(
12534                params.text_document.uri,
12535                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12536            );
12537            futures::future::pending::<()>().await;
12538            unreachable!()
12539        },
12540    );
12541    let format = editor
12542        .update_in(cx, |editor, window, cx| {
12543            editor.perform_code_action_kind(
12544                project,
12545                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12546                window,
12547                cx,
12548            )
12549        })
12550        .unwrap();
12551    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
12552    cx.executor().start_waiting();
12553    format.await;
12554    assert_eq!(
12555        editor.update(cx, |editor, cx| editor.text(cx)),
12556        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
12557    );
12558}
12559
12560#[gpui::test]
12561async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
12562    init_test(cx, |_| {});
12563
12564    let mut cx = EditorLspTestContext::new_rust(
12565        lsp::ServerCapabilities {
12566            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12567            ..Default::default()
12568        },
12569        cx,
12570    )
12571    .await;
12572
12573    cx.set_state(indoc! {"
12574        one.twoˇ
12575    "});
12576
12577    // The format request takes a long time. When it completes, it inserts
12578    // a newline and an indent before the `.`
12579    cx.lsp
12580        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
12581            let executor = cx.background_executor().clone();
12582            async move {
12583                executor.timer(Duration::from_millis(100)).await;
12584                Ok(Some(vec![lsp::TextEdit {
12585                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
12586                    new_text: "\n    ".into(),
12587                }]))
12588            }
12589        });
12590
12591    // Submit a format request.
12592    let format_1 = cx
12593        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12594        .unwrap();
12595    cx.executor().run_until_parked();
12596
12597    // Submit a second format request.
12598    let format_2 = cx
12599        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12600        .unwrap();
12601    cx.executor().run_until_parked();
12602
12603    // Wait for both format requests to complete
12604    cx.executor().advance_clock(Duration::from_millis(200));
12605    cx.executor().start_waiting();
12606    format_1.await.unwrap();
12607    cx.executor().start_waiting();
12608    format_2.await.unwrap();
12609
12610    // The formatting edits only happens once.
12611    cx.assert_editor_state(indoc! {"
12612        one
12613            .twoˇ
12614    "});
12615}
12616
12617#[gpui::test]
12618async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
12619    init_test(cx, |settings| {
12620        settings.defaults.formatter = Some(FormatterList::default())
12621    });
12622
12623    let mut cx = EditorLspTestContext::new_rust(
12624        lsp::ServerCapabilities {
12625            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12626            ..Default::default()
12627        },
12628        cx,
12629    )
12630    .await;
12631
12632    // Record which buffer changes have been sent to the language server
12633    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
12634    cx.lsp
12635        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
12636            let buffer_changes = buffer_changes.clone();
12637            move |params, _| {
12638                buffer_changes.lock().extend(
12639                    params
12640                        .content_changes
12641                        .into_iter()
12642                        .map(|e| (e.range.unwrap(), e.text)),
12643                );
12644            }
12645        });
12646    // Handle formatting requests to the language server.
12647    cx.lsp
12648        .set_request_handler::<lsp::request::Formatting, _, _>({
12649            let buffer_changes = buffer_changes.clone();
12650            move |_, _| {
12651                let buffer_changes = buffer_changes.clone();
12652                // Insert blank lines between each line of the buffer.
12653                async move {
12654                    // When formatting is requested, trailing whitespace has already been stripped,
12655                    // and the trailing newline has already been added.
12656                    assert_eq!(
12657                        &buffer_changes.lock()[1..],
12658                        &[
12659                            (
12660                                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
12661                                "".into()
12662                            ),
12663                            (
12664                                lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
12665                                "".into()
12666                            ),
12667                            (
12668                                lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
12669                                "\n".into()
12670                            ),
12671                        ]
12672                    );
12673
12674                    Ok(Some(vec![
12675                        lsp::TextEdit {
12676                            range: lsp::Range::new(
12677                                lsp::Position::new(1, 0),
12678                                lsp::Position::new(1, 0),
12679                            ),
12680                            new_text: "\n".into(),
12681                        },
12682                        lsp::TextEdit {
12683                            range: lsp::Range::new(
12684                                lsp::Position::new(2, 0),
12685                                lsp::Position::new(2, 0),
12686                            ),
12687                            new_text: "\n".into(),
12688                        },
12689                    ]))
12690                }
12691            }
12692        });
12693
12694    // Set up a buffer white some trailing whitespace and no trailing newline.
12695    cx.set_state(
12696        &[
12697            "one ",   //
12698            "twoˇ",   //
12699            "three ", //
12700            "four",   //
12701        ]
12702        .join("\n"),
12703    );
12704    cx.run_until_parked();
12705
12706    // Submit a format request.
12707    let format = cx
12708        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12709        .unwrap();
12710
12711    cx.run_until_parked();
12712    // After formatting the buffer, the trailing whitespace is stripped,
12713    // a newline is appended, and the edits provided by the language server
12714    // have been applied.
12715    format.await.unwrap();
12716
12717    cx.assert_editor_state(
12718        &[
12719            "one",   //
12720            "",      //
12721            "twoˇ",  //
12722            "",      //
12723            "three", //
12724            "four",  //
12725            "",      //
12726        ]
12727        .join("\n"),
12728    );
12729
12730    // Undoing the formatting undoes the trailing whitespace removal, the
12731    // trailing newline, and the LSP edits.
12732    cx.update_buffer(|buffer, cx| buffer.undo(cx));
12733    cx.assert_editor_state(
12734        &[
12735            "one ",   //
12736            "twoˇ",   //
12737            "three ", //
12738            "four",   //
12739        ]
12740        .join("\n"),
12741    );
12742}
12743
12744#[gpui::test]
12745async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
12746    cx: &mut TestAppContext,
12747) {
12748    init_test(cx, |_| {});
12749
12750    cx.update(|cx| {
12751        cx.update_global::<SettingsStore, _>(|settings, cx| {
12752            settings.update_user_settings(cx, |settings| {
12753                settings.editor.auto_signature_help = Some(true);
12754            });
12755        });
12756    });
12757
12758    let mut cx = EditorLspTestContext::new_rust(
12759        lsp::ServerCapabilities {
12760            signature_help_provider: Some(lsp::SignatureHelpOptions {
12761                ..Default::default()
12762            }),
12763            ..Default::default()
12764        },
12765        cx,
12766    )
12767    .await;
12768
12769    let language = Language::new(
12770        LanguageConfig {
12771            name: "Rust".into(),
12772            brackets: BracketPairConfig {
12773                pairs: vec![
12774                    BracketPair {
12775                        start: "{".to_string(),
12776                        end: "}".to_string(),
12777                        close: true,
12778                        surround: true,
12779                        newline: true,
12780                    },
12781                    BracketPair {
12782                        start: "(".to_string(),
12783                        end: ")".to_string(),
12784                        close: true,
12785                        surround: true,
12786                        newline: true,
12787                    },
12788                    BracketPair {
12789                        start: "/*".to_string(),
12790                        end: " */".to_string(),
12791                        close: true,
12792                        surround: true,
12793                        newline: true,
12794                    },
12795                    BracketPair {
12796                        start: "[".to_string(),
12797                        end: "]".to_string(),
12798                        close: false,
12799                        surround: false,
12800                        newline: true,
12801                    },
12802                    BracketPair {
12803                        start: "\"".to_string(),
12804                        end: "\"".to_string(),
12805                        close: true,
12806                        surround: true,
12807                        newline: false,
12808                    },
12809                    BracketPair {
12810                        start: "<".to_string(),
12811                        end: ">".to_string(),
12812                        close: false,
12813                        surround: true,
12814                        newline: true,
12815                    },
12816                ],
12817                ..Default::default()
12818            },
12819            autoclose_before: "})]".to_string(),
12820            ..Default::default()
12821        },
12822        Some(tree_sitter_rust::LANGUAGE.into()),
12823    );
12824    let language = Arc::new(language);
12825
12826    cx.language_registry().add(language.clone());
12827    cx.update_buffer(|buffer, cx| {
12828        buffer.set_language(Some(language), cx);
12829    });
12830
12831    cx.set_state(
12832        &r#"
12833            fn main() {
12834                sampleˇ
12835            }
12836        "#
12837        .unindent(),
12838    );
12839
12840    cx.update_editor(|editor, window, cx| {
12841        editor.handle_input("(", window, cx);
12842    });
12843    cx.assert_editor_state(
12844        &"
12845            fn main() {
12846                sample(ˇ)
12847            }
12848        "
12849        .unindent(),
12850    );
12851
12852    let mocked_response = lsp::SignatureHelp {
12853        signatures: vec![lsp::SignatureInformation {
12854            label: "fn sample(param1: u8, param2: u8)".to_string(),
12855            documentation: None,
12856            parameters: Some(vec![
12857                lsp::ParameterInformation {
12858                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12859                    documentation: None,
12860                },
12861                lsp::ParameterInformation {
12862                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12863                    documentation: None,
12864                },
12865            ]),
12866            active_parameter: None,
12867        }],
12868        active_signature: Some(0),
12869        active_parameter: Some(0),
12870    };
12871    handle_signature_help_request(&mut cx, mocked_response).await;
12872
12873    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12874        .await;
12875
12876    cx.editor(|editor, _, _| {
12877        let signature_help_state = editor.signature_help_state.popover().cloned();
12878        let signature = signature_help_state.unwrap();
12879        assert_eq!(
12880            signature.signatures[signature.current_signature].label,
12881            "fn sample(param1: u8, param2: u8)"
12882        );
12883    });
12884}
12885
12886#[gpui::test]
12887async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
12888    init_test(cx, |_| {});
12889
12890    cx.update(|cx| {
12891        cx.update_global::<SettingsStore, _>(|settings, cx| {
12892            settings.update_user_settings(cx, |settings| {
12893                settings.editor.auto_signature_help = Some(false);
12894                settings.editor.show_signature_help_after_edits = Some(false);
12895            });
12896        });
12897    });
12898
12899    let mut cx = EditorLspTestContext::new_rust(
12900        lsp::ServerCapabilities {
12901            signature_help_provider: Some(lsp::SignatureHelpOptions {
12902                ..Default::default()
12903            }),
12904            ..Default::default()
12905        },
12906        cx,
12907    )
12908    .await;
12909
12910    let language = Language::new(
12911        LanguageConfig {
12912            name: "Rust".into(),
12913            brackets: BracketPairConfig {
12914                pairs: vec![
12915                    BracketPair {
12916                        start: "{".to_string(),
12917                        end: "}".to_string(),
12918                        close: true,
12919                        surround: true,
12920                        newline: true,
12921                    },
12922                    BracketPair {
12923                        start: "(".to_string(),
12924                        end: ")".to_string(),
12925                        close: true,
12926                        surround: true,
12927                        newline: true,
12928                    },
12929                    BracketPair {
12930                        start: "/*".to_string(),
12931                        end: " */".to_string(),
12932                        close: true,
12933                        surround: true,
12934                        newline: true,
12935                    },
12936                    BracketPair {
12937                        start: "[".to_string(),
12938                        end: "]".to_string(),
12939                        close: false,
12940                        surround: false,
12941                        newline: true,
12942                    },
12943                    BracketPair {
12944                        start: "\"".to_string(),
12945                        end: "\"".to_string(),
12946                        close: true,
12947                        surround: true,
12948                        newline: false,
12949                    },
12950                    BracketPair {
12951                        start: "<".to_string(),
12952                        end: ">".to_string(),
12953                        close: false,
12954                        surround: true,
12955                        newline: true,
12956                    },
12957                ],
12958                ..Default::default()
12959            },
12960            autoclose_before: "})]".to_string(),
12961            ..Default::default()
12962        },
12963        Some(tree_sitter_rust::LANGUAGE.into()),
12964    );
12965    let language = Arc::new(language);
12966
12967    cx.language_registry().add(language.clone());
12968    cx.update_buffer(|buffer, cx| {
12969        buffer.set_language(Some(language), cx);
12970    });
12971
12972    // Ensure that signature_help is not called when no signature help is enabled.
12973    cx.set_state(
12974        &r#"
12975            fn main() {
12976                sampleˇ
12977            }
12978        "#
12979        .unindent(),
12980    );
12981    cx.update_editor(|editor, window, cx| {
12982        editor.handle_input("(", window, cx);
12983    });
12984    cx.assert_editor_state(
12985        &"
12986            fn main() {
12987                sample(ˇ)
12988            }
12989        "
12990        .unindent(),
12991    );
12992    cx.editor(|editor, _, _| {
12993        assert!(editor.signature_help_state.task().is_none());
12994    });
12995
12996    let mocked_response = lsp::SignatureHelp {
12997        signatures: vec![lsp::SignatureInformation {
12998            label: "fn sample(param1: u8, param2: u8)".to_string(),
12999            documentation: None,
13000            parameters: Some(vec![
13001                lsp::ParameterInformation {
13002                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13003                    documentation: None,
13004                },
13005                lsp::ParameterInformation {
13006                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13007                    documentation: None,
13008                },
13009            ]),
13010            active_parameter: None,
13011        }],
13012        active_signature: Some(0),
13013        active_parameter: Some(0),
13014    };
13015
13016    // Ensure that signature_help is called when enabled afte edits
13017    cx.update(|_, cx| {
13018        cx.update_global::<SettingsStore, _>(|settings, cx| {
13019            settings.update_user_settings(cx, |settings| {
13020                settings.editor.auto_signature_help = Some(false);
13021                settings.editor.show_signature_help_after_edits = Some(true);
13022            });
13023        });
13024    });
13025    cx.set_state(
13026        &r#"
13027            fn main() {
13028                sampleˇ
13029            }
13030        "#
13031        .unindent(),
13032    );
13033    cx.update_editor(|editor, window, cx| {
13034        editor.handle_input("(", window, cx);
13035    });
13036    cx.assert_editor_state(
13037        &"
13038            fn main() {
13039                sample(ˇ)
13040            }
13041        "
13042        .unindent(),
13043    );
13044    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13045    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13046        .await;
13047    cx.update_editor(|editor, _, _| {
13048        let signature_help_state = editor.signature_help_state.popover().cloned();
13049        assert!(signature_help_state.is_some());
13050        let signature = signature_help_state.unwrap();
13051        assert_eq!(
13052            signature.signatures[signature.current_signature].label,
13053            "fn sample(param1: u8, param2: u8)"
13054        );
13055        editor.signature_help_state = SignatureHelpState::default();
13056    });
13057
13058    // Ensure that signature_help is called when auto signature help override is enabled
13059    cx.update(|_, cx| {
13060        cx.update_global::<SettingsStore, _>(|settings, cx| {
13061            settings.update_user_settings(cx, |settings| {
13062                settings.editor.auto_signature_help = Some(true);
13063                settings.editor.show_signature_help_after_edits = Some(false);
13064            });
13065        });
13066    });
13067    cx.set_state(
13068        &r#"
13069            fn main() {
13070                sampleˇ
13071            }
13072        "#
13073        .unindent(),
13074    );
13075    cx.update_editor(|editor, window, cx| {
13076        editor.handle_input("(", window, cx);
13077    });
13078    cx.assert_editor_state(
13079        &"
13080            fn main() {
13081                sample(ˇ)
13082            }
13083        "
13084        .unindent(),
13085    );
13086    handle_signature_help_request(&mut cx, mocked_response).await;
13087    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13088        .await;
13089    cx.editor(|editor, _, _| {
13090        let signature_help_state = editor.signature_help_state.popover().cloned();
13091        assert!(signature_help_state.is_some());
13092        let signature = signature_help_state.unwrap();
13093        assert_eq!(
13094            signature.signatures[signature.current_signature].label,
13095            "fn sample(param1: u8, param2: u8)"
13096        );
13097    });
13098}
13099
13100#[gpui::test]
13101async fn test_signature_help(cx: &mut TestAppContext) {
13102    init_test(cx, |_| {});
13103    cx.update(|cx| {
13104        cx.update_global::<SettingsStore, _>(|settings, cx| {
13105            settings.update_user_settings(cx, |settings| {
13106                settings.editor.auto_signature_help = Some(true);
13107            });
13108        });
13109    });
13110
13111    let mut cx = EditorLspTestContext::new_rust(
13112        lsp::ServerCapabilities {
13113            signature_help_provider: Some(lsp::SignatureHelpOptions {
13114                ..Default::default()
13115            }),
13116            ..Default::default()
13117        },
13118        cx,
13119    )
13120    .await;
13121
13122    // A test that directly calls `show_signature_help`
13123    cx.update_editor(|editor, window, cx| {
13124        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13125    });
13126
13127    let mocked_response = lsp::SignatureHelp {
13128        signatures: vec![lsp::SignatureInformation {
13129            label: "fn sample(param1: u8, param2: u8)".to_string(),
13130            documentation: None,
13131            parameters: Some(vec![
13132                lsp::ParameterInformation {
13133                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13134                    documentation: None,
13135                },
13136                lsp::ParameterInformation {
13137                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13138                    documentation: None,
13139                },
13140            ]),
13141            active_parameter: None,
13142        }],
13143        active_signature: Some(0),
13144        active_parameter: Some(0),
13145    };
13146    handle_signature_help_request(&mut cx, mocked_response).await;
13147
13148    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13149        .await;
13150
13151    cx.editor(|editor, _, _| {
13152        let signature_help_state = editor.signature_help_state.popover().cloned();
13153        assert!(signature_help_state.is_some());
13154        let signature = signature_help_state.unwrap();
13155        assert_eq!(
13156            signature.signatures[signature.current_signature].label,
13157            "fn sample(param1: u8, param2: u8)"
13158        );
13159    });
13160
13161    // When exiting outside from inside the brackets, `signature_help` is closed.
13162    cx.set_state(indoc! {"
13163        fn main() {
13164            sample(ˇ);
13165        }
13166
13167        fn sample(param1: u8, param2: u8) {}
13168    "});
13169
13170    cx.update_editor(|editor, window, cx| {
13171        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13172            s.select_ranges([0..0])
13173        });
13174    });
13175
13176    let mocked_response = lsp::SignatureHelp {
13177        signatures: Vec::new(),
13178        active_signature: None,
13179        active_parameter: None,
13180    };
13181    handle_signature_help_request(&mut cx, mocked_response).await;
13182
13183    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13184        .await;
13185
13186    cx.editor(|editor, _, _| {
13187        assert!(!editor.signature_help_state.is_shown());
13188    });
13189
13190    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
13191    cx.set_state(indoc! {"
13192        fn main() {
13193            sample(ˇ);
13194        }
13195
13196        fn sample(param1: u8, param2: u8) {}
13197    "});
13198
13199    let mocked_response = lsp::SignatureHelp {
13200        signatures: vec![lsp::SignatureInformation {
13201            label: "fn sample(param1: u8, param2: u8)".to_string(),
13202            documentation: None,
13203            parameters: Some(vec![
13204                lsp::ParameterInformation {
13205                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13206                    documentation: None,
13207                },
13208                lsp::ParameterInformation {
13209                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13210                    documentation: None,
13211                },
13212            ]),
13213            active_parameter: None,
13214        }],
13215        active_signature: Some(0),
13216        active_parameter: Some(0),
13217    };
13218    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13219    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13220        .await;
13221    cx.editor(|editor, _, _| {
13222        assert!(editor.signature_help_state.is_shown());
13223    });
13224
13225    // Restore the popover with more parameter input
13226    cx.set_state(indoc! {"
13227        fn main() {
13228            sample(param1, param2ˇ);
13229        }
13230
13231        fn sample(param1: u8, param2: u8) {}
13232    "});
13233
13234    let mocked_response = lsp::SignatureHelp {
13235        signatures: vec![lsp::SignatureInformation {
13236            label: "fn sample(param1: u8, param2: u8)".to_string(),
13237            documentation: None,
13238            parameters: Some(vec![
13239                lsp::ParameterInformation {
13240                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13241                    documentation: None,
13242                },
13243                lsp::ParameterInformation {
13244                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13245                    documentation: None,
13246                },
13247            ]),
13248            active_parameter: None,
13249        }],
13250        active_signature: Some(0),
13251        active_parameter: Some(1),
13252    };
13253    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13254    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13255        .await;
13256
13257    // When selecting a range, the popover is gone.
13258    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
13259    cx.update_editor(|editor, window, cx| {
13260        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13261            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13262        })
13263    });
13264    cx.assert_editor_state(indoc! {"
13265        fn main() {
13266            sample(param1, «ˇparam2»);
13267        }
13268
13269        fn sample(param1: u8, param2: u8) {}
13270    "});
13271    cx.editor(|editor, _, _| {
13272        assert!(!editor.signature_help_state.is_shown());
13273    });
13274
13275    // When unselecting again, the popover is back if within the brackets.
13276    cx.update_editor(|editor, window, cx| {
13277        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13278            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13279        })
13280    });
13281    cx.assert_editor_state(indoc! {"
13282        fn main() {
13283            sample(param1, ˇparam2);
13284        }
13285
13286        fn sample(param1: u8, param2: u8) {}
13287    "});
13288    handle_signature_help_request(&mut cx, mocked_response).await;
13289    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13290        .await;
13291    cx.editor(|editor, _, _| {
13292        assert!(editor.signature_help_state.is_shown());
13293    });
13294
13295    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
13296    cx.update_editor(|editor, window, cx| {
13297        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13298            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
13299            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13300        })
13301    });
13302    cx.assert_editor_state(indoc! {"
13303        fn main() {
13304            sample(param1, ˇparam2);
13305        }
13306
13307        fn sample(param1: u8, param2: u8) {}
13308    "});
13309
13310    let mocked_response = lsp::SignatureHelp {
13311        signatures: vec![lsp::SignatureInformation {
13312            label: "fn sample(param1: u8, param2: u8)".to_string(),
13313            documentation: None,
13314            parameters: Some(vec![
13315                lsp::ParameterInformation {
13316                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13317                    documentation: None,
13318                },
13319                lsp::ParameterInformation {
13320                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13321                    documentation: None,
13322                },
13323            ]),
13324            active_parameter: None,
13325        }],
13326        active_signature: Some(0),
13327        active_parameter: Some(1),
13328    };
13329    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13330    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13331        .await;
13332    cx.update_editor(|editor, _, cx| {
13333        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
13334    });
13335    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13336        .await;
13337    cx.update_editor(|editor, window, cx| {
13338        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13339            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13340        })
13341    });
13342    cx.assert_editor_state(indoc! {"
13343        fn main() {
13344            sample(param1, «ˇparam2»);
13345        }
13346
13347        fn sample(param1: u8, param2: u8) {}
13348    "});
13349    cx.update_editor(|editor, window, cx| {
13350        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13351            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13352        })
13353    });
13354    cx.assert_editor_state(indoc! {"
13355        fn main() {
13356            sample(param1, ˇparam2);
13357        }
13358
13359        fn sample(param1: u8, param2: u8) {}
13360    "});
13361    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
13362        .await;
13363}
13364
13365#[gpui::test]
13366async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
13367    init_test(cx, |_| {});
13368
13369    let mut cx = EditorLspTestContext::new_rust(
13370        lsp::ServerCapabilities {
13371            signature_help_provider: Some(lsp::SignatureHelpOptions {
13372                ..Default::default()
13373            }),
13374            ..Default::default()
13375        },
13376        cx,
13377    )
13378    .await;
13379
13380    cx.set_state(indoc! {"
13381        fn main() {
13382            overloadedˇ
13383        }
13384    "});
13385
13386    cx.update_editor(|editor, window, cx| {
13387        editor.handle_input("(", window, cx);
13388        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13389    });
13390
13391    // Mock response with 3 signatures
13392    let mocked_response = lsp::SignatureHelp {
13393        signatures: vec![
13394            lsp::SignatureInformation {
13395                label: "fn overloaded(x: i32)".to_string(),
13396                documentation: None,
13397                parameters: Some(vec![lsp::ParameterInformation {
13398                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13399                    documentation: None,
13400                }]),
13401                active_parameter: None,
13402            },
13403            lsp::SignatureInformation {
13404                label: "fn overloaded(x: i32, y: i32)".to_string(),
13405                documentation: None,
13406                parameters: Some(vec![
13407                    lsp::ParameterInformation {
13408                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13409                        documentation: None,
13410                    },
13411                    lsp::ParameterInformation {
13412                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13413                        documentation: None,
13414                    },
13415                ]),
13416                active_parameter: None,
13417            },
13418            lsp::SignatureInformation {
13419                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
13420                documentation: None,
13421                parameters: Some(vec![
13422                    lsp::ParameterInformation {
13423                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13424                        documentation: None,
13425                    },
13426                    lsp::ParameterInformation {
13427                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13428                        documentation: None,
13429                    },
13430                    lsp::ParameterInformation {
13431                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
13432                        documentation: None,
13433                    },
13434                ]),
13435                active_parameter: None,
13436            },
13437        ],
13438        active_signature: Some(1),
13439        active_parameter: Some(0),
13440    };
13441    handle_signature_help_request(&mut cx, mocked_response).await;
13442
13443    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13444        .await;
13445
13446    // Verify we have multiple signatures and the right one is selected
13447    cx.editor(|editor, _, _| {
13448        let popover = editor.signature_help_state.popover().cloned().unwrap();
13449        assert_eq!(popover.signatures.len(), 3);
13450        // active_signature was 1, so that should be the current
13451        assert_eq!(popover.current_signature, 1);
13452        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
13453        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
13454        assert_eq!(
13455            popover.signatures[2].label,
13456            "fn overloaded(x: i32, y: i32, z: i32)"
13457        );
13458    });
13459
13460    // Test navigation functionality
13461    cx.update_editor(|editor, window, cx| {
13462        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13463    });
13464
13465    cx.editor(|editor, _, _| {
13466        let popover = editor.signature_help_state.popover().cloned().unwrap();
13467        assert_eq!(popover.current_signature, 2);
13468    });
13469
13470    // Test wrap around
13471    cx.update_editor(|editor, window, cx| {
13472        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13473    });
13474
13475    cx.editor(|editor, _, _| {
13476        let popover = editor.signature_help_state.popover().cloned().unwrap();
13477        assert_eq!(popover.current_signature, 0);
13478    });
13479
13480    // Test previous navigation
13481    cx.update_editor(|editor, window, cx| {
13482        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
13483    });
13484
13485    cx.editor(|editor, _, _| {
13486        let popover = editor.signature_help_state.popover().cloned().unwrap();
13487        assert_eq!(popover.current_signature, 2);
13488    });
13489}
13490
13491#[gpui::test]
13492async fn test_completion_mode(cx: &mut TestAppContext) {
13493    init_test(cx, |_| {});
13494    let mut cx = EditorLspTestContext::new_rust(
13495        lsp::ServerCapabilities {
13496            completion_provider: Some(lsp::CompletionOptions {
13497                resolve_provider: Some(true),
13498                ..Default::default()
13499            }),
13500            ..Default::default()
13501        },
13502        cx,
13503    )
13504    .await;
13505
13506    struct Run {
13507        run_description: &'static str,
13508        initial_state: String,
13509        buffer_marked_text: String,
13510        completion_label: &'static str,
13511        completion_text: &'static str,
13512        expected_with_insert_mode: String,
13513        expected_with_replace_mode: String,
13514        expected_with_replace_subsequence_mode: String,
13515        expected_with_replace_suffix_mode: String,
13516    }
13517
13518    let runs = [
13519        Run {
13520            run_description: "Start of word matches completion text",
13521            initial_state: "before ediˇ after".into(),
13522            buffer_marked_text: "before <edi|> after".into(),
13523            completion_label: "editor",
13524            completion_text: "editor",
13525            expected_with_insert_mode: "before editorˇ after".into(),
13526            expected_with_replace_mode: "before editorˇ after".into(),
13527            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13528            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13529        },
13530        Run {
13531            run_description: "Accept same text at the middle of the word",
13532            initial_state: "before ediˇtor after".into(),
13533            buffer_marked_text: "before <edi|tor> after".into(),
13534            completion_label: "editor",
13535            completion_text: "editor",
13536            expected_with_insert_mode: "before editorˇtor after".into(),
13537            expected_with_replace_mode: "before editorˇ after".into(),
13538            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13539            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13540        },
13541        Run {
13542            run_description: "End of word matches completion text -- cursor at end",
13543            initial_state: "before torˇ after".into(),
13544            buffer_marked_text: "before <tor|> after".into(),
13545            completion_label: "editor",
13546            completion_text: "editor",
13547            expected_with_insert_mode: "before editorˇ after".into(),
13548            expected_with_replace_mode: "before editorˇ after".into(),
13549            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13550            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13551        },
13552        Run {
13553            run_description: "End of word matches completion text -- cursor at start",
13554            initial_state: "before ˇtor after".into(),
13555            buffer_marked_text: "before <|tor> after".into(),
13556            completion_label: "editor",
13557            completion_text: "editor",
13558            expected_with_insert_mode: "before editorˇtor after".into(),
13559            expected_with_replace_mode: "before editorˇ after".into(),
13560            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13561            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13562        },
13563        Run {
13564            run_description: "Prepend text containing whitespace",
13565            initial_state: "pˇfield: bool".into(),
13566            buffer_marked_text: "<p|field>: bool".into(),
13567            completion_label: "pub ",
13568            completion_text: "pub ",
13569            expected_with_insert_mode: "pub ˇfield: bool".into(),
13570            expected_with_replace_mode: "pub ˇ: bool".into(),
13571            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
13572            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
13573        },
13574        Run {
13575            run_description: "Add element to start of list",
13576            initial_state: "[element_ˇelement_2]".into(),
13577            buffer_marked_text: "[<element_|element_2>]".into(),
13578            completion_label: "element_1",
13579            completion_text: "element_1",
13580            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
13581            expected_with_replace_mode: "[element_1ˇ]".into(),
13582            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
13583            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
13584        },
13585        Run {
13586            run_description: "Add element to start of list -- first and second elements are equal",
13587            initial_state: "[elˇelement]".into(),
13588            buffer_marked_text: "[<el|element>]".into(),
13589            completion_label: "element",
13590            completion_text: "element",
13591            expected_with_insert_mode: "[elementˇelement]".into(),
13592            expected_with_replace_mode: "[elementˇ]".into(),
13593            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
13594            expected_with_replace_suffix_mode: "[elementˇ]".into(),
13595        },
13596        Run {
13597            run_description: "Ends with matching suffix",
13598            initial_state: "SubˇError".into(),
13599            buffer_marked_text: "<Sub|Error>".into(),
13600            completion_label: "SubscriptionError",
13601            completion_text: "SubscriptionError",
13602            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
13603            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13604            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13605            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
13606        },
13607        Run {
13608            run_description: "Suffix is a subsequence -- contiguous",
13609            initial_state: "SubˇErr".into(),
13610            buffer_marked_text: "<Sub|Err>".into(),
13611            completion_label: "SubscriptionError",
13612            completion_text: "SubscriptionError",
13613            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
13614            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13615            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13616            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
13617        },
13618        Run {
13619            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
13620            initial_state: "Suˇscrirr".into(),
13621            buffer_marked_text: "<Su|scrirr>".into(),
13622            completion_label: "SubscriptionError",
13623            completion_text: "SubscriptionError",
13624            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
13625            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13626            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13627            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
13628        },
13629        Run {
13630            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
13631            initial_state: "foo(indˇix)".into(),
13632            buffer_marked_text: "foo(<ind|ix>)".into(),
13633            completion_label: "node_index",
13634            completion_text: "node_index",
13635            expected_with_insert_mode: "foo(node_indexˇix)".into(),
13636            expected_with_replace_mode: "foo(node_indexˇ)".into(),
13637            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
13638            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
13639        },
13640        Run {
13641            run_description: "Replace range ends before cursor - should extend to cursor",
13642            initial_state: "before editˇo after".into(),
13643            buffer_marked_text: "before <{ed}>it|o after".into(),
13644            completion_label: "editor",
13645            completion_text: "editor",
13646            expected_with_insert_mode: "before editorˇo after".into(),
13647            expected_with_replace_mode: "before editorˇo after".into(),
13648            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
13649            expected_with_replace_suffix_mode: "before editorˇo after".into(),
13650        },
13651        Run {
13652            run_description: "Uses label for suffix matching",
13653            initial_state: "before ediˇtor after".into(),
13654            buffer_marked_text: "before <edi|tor> after".into(),
13655            completion_label: "editor",
13656            completion_text: "editor()",
13657            expected_with_insert_mode: "before editor()ˇtor after".into(),
13658            expected_with_replace_mode: "before editor()ˇ after".into(),
13659            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
13660            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
13661        },
13662        Run {
13663            run_description: "Case insensitive subsequence and suffix matching",
13664            initial_state: "before EDiˇtoR after".into(),
13665            buffer_marked_text: "before <EDi|toR> after".into(),
13666            completion_label: "editor",
13667            completion_text: "editor",
13668            expected_with_insert_mode: "before editorˇtoR after".into(),
13669            expected_with_replace_mode: "before editorˇ after".into(),
13670            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13671            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13672        },
13673    ];
13674
13675    for run in runs {
13676        let run_variations = [
13677            (LspInsertMode::Insert, run.expected_with_insert_mode),
13678            (LspInsertMode::Replace, run.expected_with_replace_mode),
13679            (
13680                LspInsertMode::ReplaceSubsequence,
13681                run.expected_with_replace_subsequence_mode,
13682            ),
13683            (
13684                LspInsertMode::ReplaceSuffix,
13685                run.expected_with_replace_suffix_mode,
13686            ),
13687        ];
13688
13689        for (lsp_insert_mode, expected_text) in run_variations {
13690            eprintln!(
13691                "run = {:?}, mode = {lsp_insert_mode:.?}",
13692                run.run_description,
13693            );
13694
13695            update_test_language_settings(&mut cx, |settings| {
13696                settings.defaults.completions = Some(CompletionSettingsContent {
13697                    lsp_insert_mode: Some(lsp_insert_mode),
13698                    words: Some(WordsCompletionMode::Disabled),
13699                    words_min_length: Some(0),
13700                    ..Default::default()
13701                });
13702            });
13703
13704            cx.set_state(&run.initial_state);
13705            cx.update_editor(|editor, window, cx| {
13706                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13707            });
13708
13709            let counter = Arc::new(AtomicUsize::new(0));
13710            handle_completion_request_with_insert_and_replace(
13711                &mut cx,
13712                &run.buffer_marked_text,
13713                vec![(run.completion_label, run.completion_text)],
13714                counter.clone(),
13715            )
13716            .await;
13717            cx.condition(|editor, _| editor.context_menu_visible())
13718                .await;
13719            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13720
13721            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13722                editor
13723                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
13724                    .unwrap()
13725            });
13726            cx.assert_editor_state(&expected_text);
13727            handle_resolve_completion_request(&mut cx, None).await;
13728            apply_additional_edits.await.unwrap();
13729        }
13730    }
13731}
13732
13733#[gpui::test]
13734async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
13735    init_test(cx, |_| {});
13736    let mut cx = EditorLspTestContext::new_rust(
13737        lsp::ServerCapabilities {
13738            completion_provider: Some(lsp::CompletionOptions {
13739                resolve_provider: Some(true),
13740                ..Default::default()
13741            }),
13742            ..Default::default()
13743        },
13744        cx,
13745    )
13746    .await;
13747
13748    let initial_state = "SubˇError";
13749    let buffer_marked_text = "<Sub|Error>";
13750    let completion_text = "SubscriptionError";
13751    let expected_with_insert_mode = "SubscriptionErrorˇError";
13752    let expected_with_replace_mode = "SubscriptionErrorˇ";
13753
13754    update_test_language_settings(&mut cx, |settings| {
13755        settings.defaults.completions = Some(CompletionSettingsContent {
13756            words: Some(WordsCompletionMode::Disabled),
13757            words_min_length: Some(0),
13758            // set the opposite here to ensure that the action is overriding the default behavior
13759            lsp_insert_mode: Some(LspInsertMode::Insert),
13760            ..Default::default()
13761        });
13762    });
13763
13764    cx.set_state(initial_state);
13765    cx.update_editor(|editor, window, cx| {
13766        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13767    });
13768
13769    let counter = Arc::new(AtomicUsize::new(0));
13770    handle_completion_request_with_insert_and_replace(
13771        &mut cx,
13772        buffer_marked_text,
13773        vec![(completion_text, completion_text)],
13774        counter.clone(),
13775    )
13776    .await;
13777    cx.condition(|editor, _| editor.context_menu_visible())
13778        .await;
13779    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13780
13781    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13782        editor
13783            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13784            .unwrap()
13785    });
13786    cx.assert_editor_state(expected_with_replace_mode);
13787    handle_resolve_completion_request(&mut cx, None).await;
13788    apply_additional_edits.await.unwrap();
13789
13790    update_test_language_settings(&mut cx, |settings| {
13791        settings.defaults.completions = Some(CompletionSettingsContent {
13792            words: Some(WordsCompletionMode::Disabled),
13793            words_min_length: Some(0),
13794            // set the opposite here to ensure that the action is overriding the default behavior
13795            lsp_insert_mode: Some(LspInsertMode::Replace),
13796            ..Default::default()
13797        });
13798    });
13799
13800    cx.set_state(initial_state);
13801    cx.update_editor(|editor, window, cx| {
13802        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13803    });
13804    handle_completion_request_with_insert_and_replace(
13805        &mut cx,
13806        buffer_marked_text,
13807        vec![(completion_text, completion_text)],
13808        counter.clone(),
13809    )
13810    .await;
13811    cx.condition(|editor, _| editor.context_menu_visible())
13812        .await;
13813    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13814
13815    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13816        editor
13817            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
13818            .unwrap()
13819    });
13820    cx.assert_editor_state(expected_with_insert_mode);
13821    handle_resolve_completion_request(&mut cx, None).await;
13822    apply_additional_edits.await.unwrap();
13823}
13824
13825#[gpui::test]
13826async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
13827    init_test(cx, |_| {});
13828    let mut cx = EditorLspTestContext::new_rust(
13829        lsp::ServerCapabilities {
13830            completion_provider: Some(lsp::CompletionOptions {
13831                resolve_provider: Some(true),
13832                ..Default::default()
13833            }),
13834            ..Default::default()
13835        },
13836        cx,
13837    )
13838    .await;
13839
13840    // scenario: surrounding text matches completion text
13841    let completion_text = "to_offset";
13842    let initial_state = indoc! {"
13843        1. buf.to_offˇsuffix
13844        2. buf.to_offˇsuf
13845        3. buf.to_offˇfix
13846        4. buf.to_offˇ
13847        5. into_offˇensive
13848        6. ˇsuffix
13849        7. let ˇ //
13850        8. aaˇzz
13851        9. buf.to_off«zzzzzˇ»suffix
13852        10. buf.«ˇzzzzz»suffix
13853        11. to_off«ˇzzzzz»
13854
13855        buf.to_offˇsuffix  // newest cursor
13856    "};
13857    let completion_marked_buffer = indoc! {"
13858        1. buf.to_offsuffix
13859        2. buf.to_offsuf
13860        3. buf.to_offfix
13861        4. buf.to_off
13862        5. into_offensive
13863        6. suffix
13864        7. let  //
13865        8. aazz
13866        9. buf.to_offzzzzzsuffix
13867        10. buf.zzzzzsuffix
13868        11. to_offzzzzz
13869
13870        buf.<to_off|suffix>  // newest cursor
13871    "};
13872    let expected = indoc! {"
13873        1. buf.to_offsetˇ
13874        2. buf.to_offsetˇsuf
13875        3. buf.to_offsetˇfix
13876        4. buf.to_offsetˇ
13877        5. into_offsetˇensive
13878        6. to_offsetˇsuffix
13879        7. let to_offsetˇ //
13880        8. aato_offsetˇzz
13881        9. buf.to_offsetˇ
13882        10. buf.to_offsetˇsuffix
13883        11. to_offsetˇ
13884
13885        buf.to_offsetˇ  // newest cursor
13886    "};
13887    cx.set_state(initial_state);
13888    cx.update_editor(|editor, window, cx| {
13889        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13890    });
13891    handle_completion_request_with_insert_and_replace(
13892        &mut cx,
13893        completion_marked_buffer,
13894        vec![(completion_text, completion_text)],
13895        Arc::new(AtomicUsize::new(0)),
13896    )
13897    .await;
13898    cx.condition(|editor, _| editor.context_menu_visible())
13899        .await;
13900    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13901        editor
13902            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13903            .unwrap()
13904    });
13905    cx.assert_editor_state(expected);
13906    handle_resolve_completion_request(&mut cx, None).await;
13907    apply_additional_edits.await.unwrap();
13908
13909    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
13910    let completion_text = "foo_and_bar";
13911    let initial_state = indoc! {"
13912        1. ooanbˇ
13913        2. zooanbˇ
13914        3. ooanbˇz
13915        4. zooanbˇz
13916        5. ooanˇ
13917        6. oanbˇ
13918
13919        ooanbˇ
13920    "};
13921    let completion_marked_buffer = indoc! {"
13922        1. ooanb
13923        2. zooanb
13924        3. ooanbz
13925        4. zooanbz
13926        5. ooan
13927        6. oanb
13928
13929        <ooanb|>
13930    "};
13931    let expected = indoc! {"
13932        1. foo_and_barˇ
13933        2. zfoo_and_barˇ
13934        3. foo_and_barˇz
13935        4. zfoo_and_barˇz
13936        5. ooanfoo_and_barˇ
13937        6. oanbfoo_and_barˇ
13938
13939        foo_and_barˇ
13940    "};
13941    cx.set_state(initial_state);
13942    cx.update_editor(|editor, window, cx| {
13943        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13944    });
13945    handle_completion_request_with_insert_and_replace(
13946        &mut cx,
13947        completion_marked_buffer,
13948        vec![(completion_text, completion_text)],
13949        Arc::new(AtomicUsize::new(0)),
13950    )
13951    .await;
13952    cx.condition(|editor, _| editor.context_menu_visible())
13953        .await;
13954    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13955        editor
13956            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13957            .unwrap()
13958    });
13959    cx.assert_editor_state(expected);
13960    handle_resolve_completion_request(&mut cx, None).await;
13961    apply_additional_edits.await.unwrap();
13962
13963    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
13964    // (expects the same as if it was inserted at the end)
13965    let completion_text = "foo_and_bar";
13966    let initial_state = indoc! {"
13967        1. ooˇanb
13968        2. zooˇanb
13969        3. ooˇanbz
13970        4. zooˇanbz
13971
13972        ooˇanb
13973    "};
13974    let completion_marked_buffer = indoc! {"
13975        1. ooanb
13976        2. zooanb
13977        3. ooanbz
13978        4. zooanbz
13979
13980        <oo|anb>
13981    "};
13982    let expected = indoc! {"
13983        1. foo_and_barˇ
13984        2. zfoo_and_barˇ
13985        3. foo_and_barˇz
13986        4. zfoo_and_barˇz
13987
13988        foo_and_barˇ
13989    "};
13990    cx.set_state(initial_state);
13991    cx.update_editor(|editor, window, cx| {
13992        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13993    });
13994    handle_completion_request_with_insert_and_replace(
13995        &mut cx,
13996        completion_marked_buffer,
13997        vec![(completion_text, completion_text)],
13998        Arc::new(AtomicUsize::new(0)),
13999    )
14000    .await;
14001    cx.condition(|editor, _| editor.context_menu_visible())
14002        .await;
14003    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14004        editor
14005            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14006            .unwrap()
14007    });
14008    cx.assert_editor_state(expected);
14009    handle_resolve_completion_request(&mut cx, None).await;
14010    apply_additional_edits.await.unwrap();
14011}
14012
14013// This used to crash
14014#[gpui::test]
14015async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
14016    init_test(cx, |_| {});
14017
14018    let buffer_text = indoc! {"
14019        fn main() {
14020            10.satu;
14021
14022            //
14023            // separate cursors so they open in different excerpts (manually reproducible)
14024            //
14025
14026            10.satu20;
14027        }
14028    "};
14029    let multibuffer_text_with_selections = indoc! {"
14030        fn main() {
14031            10.satuˇ;
14032
14033            //
14034
14035            //
14036
14037            10.satuˇ20;
14038        }
14039    "};
14040    let expected_multibuffer = indoc! {"
14041        fn main() {
14042            10.saturating_sub()ˇ;
14043
14044            //
14045
14046            //
14047
14048            10.saturating_sub()ˇ;
14049        }
14050    "};
14051
14052    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
14053    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
14054
14055    let fs = FakeFs::new(cx.executor());
14056    fs.insert_tree(
14057        path!("/a"),
14058        json!({
14059            "main.rs": buffer_text,
14060        }),
14061    )
14062    .await;
14063
14064    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14065    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14066    language_registry.add(rust_lang());
14067    let mut fake_servers = language_registry.register_fake_lsp(
14068        "Rust",
14069        FakeLspAdapter {
14070            capabilities: lsp::ServerCapabilities {
14071                completion_provider: Some(lsp::CompletionOptions {
14072                    resolve_provider: None,
14073                    ..lsp::CompletionOptions::default()
14074                }),
14075                ..lsp::ServerCapabilities::default()
14076            },
14077            ..FakeLspAdapter::default()
14078        },
14079    );
14080    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14081    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14082    let buffer = project
14083        .update(cx, |project, cx| {
14084            project.open_local_buffer(path!("/a/main.rs"), cx)
14085        })
14086        .await
14087        .unwrap();
14088
14089    let multi_buffer = cx.new(|cx| {
14090        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
14091        multi_buffer.push_excerpts(
14092            buffer.clone(),
14093            [ExcerptRange::new(0..first_excerpt_end)],
14094            cx,
14095        );
14096        multi_buffer.push_excerpts(
14097            buffer.clone(),
14098            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
14099            cx,
14100        );
14101        multi_buffer
14102    });
14103
14104    let editor = workspace
14105        .update(cx, |_, window, cx| {
14106            cx.new(|cx| {
14107                Editor::new(
14108                    EditorMode::Full {
14109                        scale_ui_elements_with_buffer_font_size: false,
14110                        show_active_line_background: false,
14111                        sizing_behavior: SizingBehavior::Default,
14112                    },
14113                    multi_buffer.clone(),
14114                    Some(project.clone()),
14115                    window,
14116                    cx,
14117                )
14118            })
14119        })
14120        .unwrap();
14121
14122    let pane = workspace
14123        .update(cx, |workspace, _, _| workspace.active_pane().clone())
14124        .unwrap();
14125    pane.update_in(cx, |pane, window, cx| {
14126        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
14127    });
14128
14129    let fake_server = fake_servers.next().await.unwrap();
14130
14131    editor.update_in(cx, |editor, window, cx| {
14132        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14133            s.select_ranges([
14134                Point::new(1, 11)..Point::new(1, 11),
14135                Point::new(7, 11)..Point::new(7, 11),
14136            ])
14137        });
14138
14139        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
14140    });
14141
14142    editor.update_in(cx, |editor, window, cx| {
14143        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14144    });
14145
14146    fake_server
14147        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14148            let completion_item = lsp::CompletionItem {
14149                label: "saturating_sub()".into(),
14150                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
14151                    lsp::InsertReplaceEdit {
14152                        new_text: "saturating_sub()".to_owned(),
14153                        insert: lsp::Range::new(
14154                            lsp::Position::new(7, 7),
14155                            lsp::Position::new(7, 11),
14156                        ),
14157                        replace: lsp::Range::new(
14158                            lsp::Position::new(7, 7),
14159                            lsp::Position::new(7, 13),
14160                        ),
14161                    },
14162                )),
14163                ..lsp::CompletionItem::default()
14164            };
14165
14166            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
14167        })
14168        .next()
14169        .await
14170        .unwrap();
14171
14172    cx.condition(&editor, |editor, _| editor.context_menu_visible())
14173        .await;
14174
14175    editor
14176        .update_in(cx, |editor, window, cx| {
14177            editor
14178                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14179                .unwrap()
14180        })
14181        .await
14182        .unwrap();
14183
14184    editor.update(cx, |editor, cx| {
14185        assert_text_with_selections(editor, expected_multibuffer, cx);
14186    })
14187}
14188
14189#[gpui::test]
14190async fn test_completion(cx: &mut TestAppContext) {
14191    init_test(cx, |_| {});
14192
14193    let mut cx = EditorLspTestContext::new_rust(
14194        lsp::ServerCapabilities {
14195            completion_provider: Some(lsp::CompletionOptions {
14196                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14197                resolve_provider: Some(true),
14198                ..Default::default()
14199            }),
14200            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14201            ..Default::default()
14202        },
14203        cx,
14204    )
14205    .await;
14206    let counter = Arc::new(AtomicUsize::new(0));
14207
14208    cx.set_state(indoc! {"
14209        oneˇ
14210        two
14211        three
14212    "});
14213    cx.simulate_keystroke(".");
14214    handle_completion_request(
14215        indoc! {"
14216            one.|<>
14217            two
14218            three
14219        "},
14220        vec!["first_completion", "second_completion"],
14221        true,
14222        counter.clone(),
14223        &mut cx,
14224    )
14225    .await;
14226    cx.condition(|editor, _| editor.context_menu_visible())
14227        .await;
14228    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14229
14230    let _handler = handle_signature_help_request(
14231        &mut cx,
14232        lsp::SignatureHelp {
14233            signatures: vec![lsp::SignatureInformation {
14234                label: "test signature".to_string(),
14235                documentation: None,
14236                parameters: Some(vec![lsp::ParameterInformation {
14237                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
14238                    documentation: None,
14239                }]),
14240                active_parameter: None,
14241            }],
14242            active_signature: None,
14243            active_parameter: None,
14244        },
14245    );
14246    cx.update_editor(|editor, window, cx| {
14247        assert!(
14248            !editor.signature_help_state.is_shown(),
14249            "No signature help was called for"
14250        );
14251        editor.show_signature_help(&ShowSignatureHelp, window, cx);
14252    });
14253    cx.run_until_parked();
14254    cx.update_editor(|editor, _, _| {
14255        assert!(
14256            !editor.signature_help_state.is_shown(),
14257            "No signature help should be shown when completions menu is open"
14258        );
14259    });
14260
14261    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14262        editor.context_menu_next(&Default::default(), window, cx);
14263        editor
14264            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14265            .unwrap()
14266    });
14267    cx.assert_editor_state(indoc! {"
14268        one.second_completionˇ
14269        two
14270        three
14271    "});
14272
14273    handle_resolve_completion_request(
14274        &mut cx,
14275        Some(vec![
14276            (
14277                //This overlaps with the primary completion edit which is
14278                //misbehavior from the LSP spec, test that we filter it out
14279                indoc! {"
14280                    one.second_ˇcompletion
14281                    two
14282                    threeˇ
14283                "},
14284                "overlapping additional edit",
14285            ),
14286            (
14287                indoc! {"
14288                    one.second_completion
14289                    two
14290                    threeˇ
14291                "},
14292                "\nadditional edit",
14293            ),
14294        ]),
14295    )
14296    .await;
14297    apply_additional_edits.await.unwrap();
14298    cx.assert_editor_state(indoc! {"
14299        one.second_completionˇ
14300        two
14301        three
14302        additional edit
14303    "});
14304
14305    cx.set_state(indoc! {"
14306        one.second_completion
14307        twoˇ
14308        threeˇ
14309        additional edit
14310    "});
14311    cx.simulate_keystroke(" ");
14312    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14313    cx.simulate_keystroke("s");
14314    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14315
14316    cx.assert_editor_state(indoc! {"
14317        one.second_completion
14318        two sˇ
14319        three sˇ
14320        additional edit
14321    "});
14322    handle_completion_request(
14323        indoc! {"
14324            one.second_completion
14325            two s
14326            three <s|>
14327            additional edit
14328        "},
14329        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14330        true,
14331        counter.clone(),
14332        &mut cx,
14333    )
14334    .await;
14335    cx.condition(|editor, _| editor.context_menu_visible())
14336        .await;
14337    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14338
14339    cx.simulate_keystroke("i");
14340
14341    handle_completion_request(
14342        indoc! {"
14343            one.second_completion
14344            two si
14345            three <si|>
14346            additional edit
14347        "},
14348        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14349        true,
14350        counter.clone(),
14351        &mut cx,
14352    )
14353    .await;
14354    cx.condition(|editor, _| editor.context_menu_visible())
14355        .await;
14356    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14357
14358    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14359        editor
14360            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14361            .unwrap()
14362    });
14363    cx.assert_editor_state(indoc! {"
14364        one.second_completion
14365        two sixth_completionˇ
14366        three sixth_completionˇ
14367        additional edit
14368    "});
14369
14370    apply_additional_edits.await.unwrap();
14371
14372    update_test_language_settings(&mut cx, |settings| {
14373        settings.defaults.show_completions_on_input = Some(false);
14374    });
14375    cx.set_state("editorˇ");
14376    cx.simulate_keystroke(".");
14377    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14378    cx.simulate_keystrokes("c l o");
14379    cx.assert_editor_state("editor.cloˇ");
14380    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14381    cx.update_editor(|editor, window, cx| {
14382        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14383    });
14384    handle_completion_request(
14385        "editor.<clo|>",
14386        vec!["close", "clobber"],
14387        true,
14388        counter.clone(),
14389        &mut cx,
14390    )
14391    .await;
14392    cx.condition(|editor, _| editor.context_menu_visible())
14393        .await;
14394    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
14395
14396    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14397        editor
14398            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14399            .unwrap()
14400    });
14401    cx.assert_editor_state("editor.clobberˇ");
14402    handle_resolve_completion_request(&mut cx, None).await;
14403    apply_additional_edits.await.unwrap();
14404}
14405
14406#[gpui::test]
14407async fn test_completion_reuse(cx: &mut TestAppContext) {
14408    init_test(cx, |_| {});
14409
14410    let mut cx = EditorLspTestContext::new_rust(
14411        lsp::ServerCapabilities {
14412            completion_provider: Some(lsp::CompletionOptions {
14413                trigger_characters: Some(vec![".".to_string()]),
14414                ..Default::default()
14415            }),
14416            ..Default::default()
14417        },
14418        cx,
14419    )
14420    .await;
14421
14422    let counter = Arc::new(AtomicUsize::new(0));
14423    cx.set_state("objˇ");
14424    cx.simulate_keystroke(".");
14425
14426    // Initial completion request returns complete results
14427    let is_incomplete = false;
14428    handle_completion_request(
14429        "obj.|<>",
14430        vec!["a", "ab", "abc"],
14431        is_incomplete,
14432        counter.clone(),
14433        &mut cx,
14434    )
14435    .await;
14436    cx.run_until_parked();
14437    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14438    cx.assert_editor_state("obj.ˇ");
14439    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14440
14441    // Type "a" - filters existing completions
14442    cx.simulate_keystroke("a");
14443    cx.run_until_parked();
14444    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14445    cx.assert_editor_state("obj.aˇ");
14446    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14447
14448    // Type "b" - filters existing completions
14449    cx.simulate_keystroke("b");
14450    cx.run_until_parked();
14451    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14452    cx.assert_editor_state("obj.abˇ");
14453    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14454
14455    // Type "c" - filters existing completions
14456    cx.simulate_keystroke("c");
14457    cx.run_until_parked();
14458    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14459    cx.assert_editor_state("obj.abcˇ");
14460    check_displayed_completions(vec!["abc"], &mut cx);
14461
14462    // Backspace to delete "c" - filters existing completions
14463    cx.update_editor(|editor, window, cx| {
14464        editor.backspace(&Backspace, window, cx);
14465    });
14466    cx.run_until_parked();
14467    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14468    cx.assert_editor_state("obj.abˇ");
14469    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14470
14471    // Moving cursor to the left dismisses menu.
14472    cx.update_editor(|editor, window, cx| {
14473        editor.move_left(&MoveLeft, window, cx);
14474    });
14475    cx.run_until_parked();
14476    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14477    cx.assert_editor_state("obj.aˇb");
14478    cx.update_editor(|editor, _, _| {
14479        assert_eq!(editor.context_menu_visible(), false);
14480    });
14481
14482    // Type "b" - new request
14483    cx.simulate_keystroke("b");
14484    let is_incomplete = false;
14485    handle_completion_request(
14486        "obj.<ab|>a",
14487        vec!["ab", "abc"],
14488        is_incomplete,
14489        counter.clone(),
14490        &mut cx,
14491    )
14492    .await;
14493    cx.run_until_parked();
14494    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14495    cx.assert_editor_state("obj.abˇb");
14496    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14497
14498    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
14499    cx.update_editor(|editor, window, cx| {
14500        editor.backspace(&Backspace, window, cx);
14501    });
14502    let is_incomplete = false;
14503    handle_completion_request(
14504        "obj.<a|>b",
14505        vec!["a", "ab", "abc"],
14506        is_incomplete,
14507        counter.clone(),
14508        &mut cx,
14509    )
14510    .await;
14511    cx.run_until_parked();
14512    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14513    cx.assert_editor_state("obj.aˇb");
14514    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14515
14516    // Backspace to delete "a" - dismisses menu.
14517    cx.update_editor(|editor, window, cx| {
14518        editor.backspace(&Backspace, window, cx);
14519    });
14520    cx.run_until_parked();
14521    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14522    cx.assert_editor_state("obj.ˇb");
14523    cx.update_editor(|editor, _, _| {
14524        assert_eq!(editor.context_menu_visible(), false);
14525    });
14526}
14527
14528#[gpui::test]
14529async fn test_word_completion(cx: &mut TestAppContext) {
14530    let lsp_fetch_timeout_ms = 10;
14531    init_test(cx, |language_settings| {
14532        language_settings.defaults.completions = Some(CompletionSettingsContent {
14533            words_min_length: Some(0),
14534            lsp_fetch_timeout_ms: Some(10),
14535            lsp_insert_mode: Some(LspInsertMode::Insert),
14536            ..Default::default()
14537        });
14538    });
14539
14540    let mut cx = EditorLspTestContext::new_rust(
14541        lsp::ServerCapabilities {
14542            completion_provider: Some(lsp::CompletionOptions {
14543                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14544                ..lsp::CompletionOptions::default()
14545            }),
14546            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14547            ..lsp::ServerCapabilities::default()
14548        },
14549        cx,
14550    )
14551    .await;
14552
14553    let throttle_completions = Arc::new(AtomicBool::new(false));
14554
14555    let lsp_throttle_completions = throttle_completions.clone();
14556    let _completion_requests_handler =
14557        cx.lsp
14558            .server
14559            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
14560                let lsp_throttle_completions = lsp_throttle_completions.clone();
14561                let cx = cx.clone();
14562                async move {
14563                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
14564                        cx.background_executor()
14565                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
14566                            .await;
14567                    }
14568                    Ok(Some(lsp::CompletionResponse::Array(vec![
14569                        lsp::CompletionItem {
14570                            label: "first".into(),
14571                            ..lsp::CompletionItem::default()
14572                        },
14573                        lsp::CompletionItem {
14574                            label: "last".into(),
14575                            ..lsp::CompletionItem::default()
14576                        },
14577                    ])))
14578                }
14579            });
14580
14581    cx.set_state(indoc! {"
14582        oneˇ
14583        two
14584        three
14585    "});
14586    cx.simulate_keystroke(".");
14587    cx.executor().run_until_parked();
14588    cx.condition(|editor, _| editor.context_menu_visible())
14589        .await;
14590    cx.update_editor(|editor, window, cx| {
14591        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14592        {
14593            assert_eq!(
14594                completion_menu_entries(menu),
14595                &["first", "last"],
14596                "When LSP server is fast to reply, no fallback word completions are used"
14597            );
14598        } else {
14599            panic!("expected completion menu to be open");
14600        }
14601        editor.cancel(&Cancel, window, cx);
14602    });
14603    cx.executor().run_until_parked();
14604    cx.condition(|editor, _| !editor.context_menu_visible())
14605        .await;
14606
14607    throttle_completions.store(true, atomic::Ordering::Release);
14608    cx.simulate_keystroke(".");
14609    cx.executor()
14610        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
14611    cx.executor().run_until_parked();
14612    cx.condition(|editor, _| editor.context_menu_visible())
14613        .await;
14614    cx.update_editor(|editor, _, _| {
14615        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14616        {
14617            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
14618                "When LSP server is slow, document words can be shown instead, if configured accordingly");
14619        } else {
14620            panic!("expected completion menu to be open");
14621        }
14622    });
14623}
14624
14625#[gpui::test]
14626async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
14627    init_test(cx, |language_settings| {
14628        language_settings.defaults.completions = Some(CompletionSettingsContent {
14629            words: Some(WordsCompletionMode::Enabled),
14630            words_min_length: Some(0),
14631            lsp_insert_mode: Some(LspInsertMode::Insert),
14632            ..Default::default()
14633        });
14634    });
14635
14636    let mut cx = EditorLspTestContext::new_rust(
14637        lsp::ServerCapabilities {
14638            completion_provider: Some(lsp::CompletionOptions {
14639                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14640                ..lsp::CompletionOptions::default()
14641            }),
14642            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14643            ..lsp::ServerCapabilities::default()
14644        },
14645        cx,
14646    )
14647    .await;
14648
14649    let _completion_requests_handler =
14650        cx.lsp
14651            .server
14652            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14653                Ok(Some(lsp::CompletionResponse::Array(vec![
14654                    lsp::CompletionItem {
14655                        label: "first".into(),
14656                        ..lsp::CompletionItem::default()
14657                    },
14658                    lsp::CompletionItem {
14659                        label: "last".into(),
14660                        ..lsp::CompletionItem::default()
14661                    },
14662                ])))
14663            });
14664
14665    cx.set_state(indoc! {"ˇ
14666        first
14667        last
14668        second
14669    "});
14670    cx.simulate_keystroke(".");
14671    cx.executor().run_until_parked();
14672    cx.condition(|editor, _| editor.context_menu_visible())
14673        .await;
14674    cx.update_editor(|editor, _, _| {
14675        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14676        {
14677            assert_eq!(
14678                completion_menu_entries(menu),
14679                &["first", "last", "second"],
14680                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
14681            );
14682        } else {
14683            panic!("expected completion menu to be open");
14684        }
14685    });
14686}
14687
14688#[gpui::test]
14689async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
14690    init_test(cx, |language_settings| {
14691        language_settings.defaults.completions = Some(CompletionSettingsContent {
14692            words: Some(WordsCompletionMode::Disabled),
14693            words_min_length: Some(0),
14694            lsp_insert_mode: Some(LspInsertMode::Insert),
14695            ..Default::default()
14696        });
14697    });
14698
14699    let mut cx = EditorLspTestContext::new_rust(
14700        lsp::ServerCapabilities {
14701            completion_provider: Some(lsp::CompletionOptions {
14702                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14703                ..lsp::CompletionOptions::default()
14704            }),
14705            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14706            ..lsp::ServerCapabilities::default()
14707        },
14708        cx,
14709    )
14710    .await;
14711
14712    let _completion_requests_handler =
14713        cx.lsp
14714            .server
14715            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14716                panic!("LSP completions should not be queried when dealing with word completions")
14717            });
14718
14719    cx.set_state(indoc! {"ˇ
14720        first
14721        last
14722        second
14723    "});
14724    cx.update_editor(|editor, window, cx| {
14725        editor.show_word_completions(&ShowWordCompletions, window, cx);
14726    });
14727    cx.executor().run_until_parked();
14728    cx.condition(|editor, _| editor.context_menu_visible())
14729        .await;
14730    cx.update_editor(|editor, _, _| {
14731        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14732        {
14733            assert_eq!(
14734                completion_menu_entries(menu),
14735                &["first", "last", "second"],
14736                "`ShowWordCompletions` action should show word completions"
14737            );
14738        } else {
14739            panic!("expected completion menu to be open");
14740        }
14741    });
14742
14743    cx.simulate_keystroke("l");
14744    cx.executor().run_until_parked();
14745    cx.condition(|editor, _| editor.context_menu_visible())
14746        .await;
14747    cx.update_editor(|editor, _, _| {
14748        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14749        {
14750            assert_eq!(
14751                completion_menu_entries(menu),
14752                &["last"],
14753                "After showing word completions, further editing should filter them and not query the LSP"
14754            );
14755        } else {
14756            panic!("expected completion menu to be open");
14757        }
14758    });
14759}
14760
14761#[gpui::test]
14762async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
14763    init_test(cx, |language_settings| {
14764        language_settings.defaults.completions = Some(CompletionSettingsContent {
14765            words_min_length: Some(0),
14766            lsp: Some(false),
14767            lsp_insert_mode: Some(LspInsertMode::Insert),
14768            ..Default::default()
14769        });
14770    });
14771
14772    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14773
14774    cx.set_state(indoc! {"ˇ
14775        0_usize
14776        let
14777        33
14778        4.5f32
14779    "});
14780    cx.update_editor(|editor, window, cx| {
14781        editor.show_completions(&ShowCompletions::default(), window, cx);
14782    });
14783    cx.executor().run_until_parked();
14784    cx.condition(|editor, _| editor.context_menu_visible())
14785        .await;
14786    cx.update_editor(|editor, window, cx| {
14787        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14788        {
14789            assert_eq!(
14790                completion_menu_entries(menu),
14791                &["let"],
14792                "With no digits in the completion query, no digits should be in the word completions"
14793            );
14794        } else {
14795            panic!("expected completion menu to be open");
14796        }
14797        editor.cancel(&Cancel, window, cx);
14798    });
14799
14800    cx.set_state(indoc! {"14801        0_usize
14802        let
14803        3
14804        33.35f32
14805    "});
14806    cx.update_editor(|editor, window, cx| {
14807        editor.show_completions(&ShowCompletions::default(), window, cx);
14808    });
14809    cx.executor().run_until_parked();
14810    cx.condition(|editor, _| editor.context_menu_visible())
14811        .await;
14812    cx.update_editor(|editor, _, _| {
14813        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14814        {
14815            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
14816                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
14817        } else {
14818            panic!("expected completion menu to be open");
14819        }
14820    });
14821}
14822
14823#[gpui::test]
14824async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
14825    init_test(cx, |language_settings| {
14826        language_settings.defaults.completions = Some(CompletionSettingsContent {
14827            words: Some(WordsCompletionMode::Enabled),
14828            words_min_length: Some(3),
14829            lsp_insert_mode: Some(LspInsertMode::Insert),
14830            ..Default::default()
14831        });
14832    });
14833
14834    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14835    cx.set_state(indoc! {"ˇ
14836        wow
14837        wowen
14838        wowser
14839    "});
14840    cx.simulate_keystroke("w");
14841    cx.executor().run_until_parked();
14842    cx.update_editor(|editor, _, _| {
14843        if editor.context_menu.borrow_mut().is_some() {
14844            panic!(
14845                "expected completion menu to be hidden, as words completion threshold is not met"
14846            );
14847        }
14848    });
14849
14850    cx.update_editor(|editor, window, cx| {
14851        editor.show_word_completions(&ShowWordCompletions, window, cx);
14852    });
14853    cx.executor().run_until_parked();
14854    cx.update_editor(|editor, window, cx| {
14855        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14856        {
14857            assert_eq!(completion_menu_entries(menu), &["wowser", "wowen", "wow"], "Even though the threshold is not met, invoking word completions with an action should provide the completions");
14858        } else {
14859            panic!("expected completion menu to be open after the word completions are called with an action");
14860        }
14861
14862        editor.cancel(&Cancel, window, cx);
14863    });
14864    cx.update_editor(|editor, _, _| {
14865        if editor.context_menu.borrow_mut().is_some() {
14866            panic!("expected completion menu to be hidden after canceling");
14867        }
14868    });
14869
14870    cx.simulate_keystroke("o");
14871    cx.executor().run_until_parked();
14872    cx.update_editor(|editor, _, _| {
14873        if editor.context_menu.borrow_mut().is_some() {
14874            panic!(
14875                "expected completion menu to be hidden, as words completion threshold is not met still"
14876            );
14877        }
14878    });
14879
14880    cx.simulate_keystroke("w");
14881    cx.executor().run_until_parked();
14882    cx.update_editor(|editor, _, _| {
14883        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14884        {
14885            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
14886        } else {
14887            panic!("expected completion menu to be open after the word completions threshold is met");
14888        }
14889    });
14890}
14891
14892#[gpui::test]
14893async fn test_word_completions_disabled(cx: &mut TestAppContext) {
14894    init_test(cx, |language_settings| {
14895        language_settings.defaults.completions = Some(CompletionSettingsContent {
14896            words: Some(WordsCompletionMode::Enabled),
14897            words_min_length: Some(0),
14898            lsp_insert_mode: Some(LspInsertMode::Insert),
14899            ..Default::default()
14900        });
14901    });
14902
14903    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14904    cx.update_editor(|editor, _, _| {
14905        editor.disable_word_completions();
14906    });
14907    cx.set_state(indoc! {"ˇ
14908        wow
14909        wowen
14910        wowser
14911    "});
14912    cx.simulate_keystroke("w");
14913    cx.executor().run_until_parked();
14914    cx.update_editor(|editor, _, _| {
14915        if editor.context_menu.borrow_mut().is_some() {
14916            panic!(
14917                "expected completion menu to be hidden, as words completion are disabled for this editor"
14918            );
14919        }
14920    });
14921
14922    cx.update_editor(|editor, window, cx| {
14923        editor.show_word_completions(&ShowWordCompletions, window, cx);
14924    });
14925    cx.executor().run_until_parked();
14926    cx.update_editor(|editor, _, _| {
14927        if editor.context_menu.borrow_mut().is_some() {
14928            panic!(
14929                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
14930            );
14931        }
14932    });
14933}
14934
14935fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
14936    let position = || lsp::Position {
14937        line: params.text_document_position.position.line,
14938        character: params.text_document_position.position.character,
14939    };
14940    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14941        range: lsp::Range {
14942            start: position(),
14943            end: position(),
14944        },
14945        new_text: text.to_string(),
14946    }))
14947}
14948
14949#[gpui::test]
14950async fn test_multiline_completion(cx: &mut TestAppContext) {
14951    init_test(cx, |_| {});
14952
14953    let fs = FakeFs::new(cx.executor());
14954    fs.insert_tree(
14955        path!("/a"),
14956        json!({
14957            "main.ts": "a",
14958        }),
14959    )
14960    .await;
14961
14962    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14963    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14964    let typescript_language = Arc::new(Language::new(
14965        LanguageConfig {
14966            name: "TypeScript".into(),
14967            matcher: LanguageMatcher {
14968                path_suffixes: vec!["ts".to_string()],
14969                ..LanguageMatcher::default()
14970            },
14971            line_comments: vec!["// ".into()],
14972            ..LanguageConfig::default()
14973        },
14974        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
14975    ));
14976    language_registry.add(typescript_language.clone());
14977    let mut fake_servers = language_registry.register_fake_lsp(
14978        "TypeScript",
14979        FakeLspAdapter {
14980            capabilities: lsp::ServerCapabilities {
14981                completion_provider: Some(lsp::CompletionOptions {
14982                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14983                    ..lsp::CompletionOptions::default()
14984                }),
14985                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14986                ..lsp::ServerCapabilities::default()
14987            },
14988            // Emulate vtsls label generation
14989            label_for_completion: Some(Box::new(|item, _| {
14990                let text = if let Some(description) = item
14991                    .label_details
14992                    .as_ref()
14993                    .and_then(|label_details| label_details.description.as_ref())
14994                {
14995                    format!("{} {}", item.label, description)
14996                } else if let Some(detail) = &item.detail {
14997                    format!("{} {}", item.label, detail)
14998                } else {
14999                    item.label.clone()
15000                };
15001                Some(language::CodeLabel::plain(text, None))
15002            })),
15003            ..FakeLspAdapter::default()
15004        },
15005    );
15006    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15007    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15008    let worktree_id = workspace
15009        .update(cx, |workspace, _window, cx| {
15010            workspace.project().update(cx, |project, cx| {
15011                project.worktrees(cx).next().unwrap().read(cx).id()
15012            })
15013        })
15014        .unwrap();
15015    let _buffer = project
15016        .update(cx, |project, cx| {
15017            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
15018        })
15019        .await
15020        .unwrap();
15021    let editor = workspace
15022        .update(cx, |workspace, window, cx| {
15023            workspace.open_path((worktree_id, rel_path("main.ts")), None, true, window, cx)
15024        })
15025        .unwrap()
15026        .await
15027        .unwrap()
15028        .downcast::<Editor>()
15029        .unwrap();
15030    let fake_server = fake_servers.next().await.unwrap();
15031
15032    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
15033    let multiline_label_2 = "a\nb\nc\n";
15034    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
15035    let multiline_description = "d\ne\nf\n";
15036    let multiline_detail_2 = "g\nh\ni\n";
15037
15038    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
15039        move |params, _| async move {
15040            Ok(Some(lsp::CompletionResponse::Array(vec![
15041                lsp::CompletionItem {
15042                    label: multiline_label.to_string(),
15043                    text_edit: gen_text_edit(&params, "new_text_1"),
15044                    ..lsp::CompletionItem::default()
15045                },
15046                lsp::CompletionItem {
15047                    label: "single line label 1".to_string(),
15048                    detail: Some(multiline_detail.to_string()),
15049                    text_edit: gen_text_edit(&params, "new_text_2"),
15050                    ..lsp::CompletionItem::default()
15051                },
15052                lsp::CompletionItem {
15053                    label: "single line label 2".to_string(),
15054                    label_details: Some(lsp::CompletionItemLabelDetails {
15055                        description: Some(multiline_description.to_string()),
15056                        detail: None,
15057                    }),
15058                    text_edit: gen_text_edit(&params, "new_text_2"),
15059                    ..lsp::CompletionItem::default()
15060                },
15061                lsp::CompletionItem {
15062                    label: multiline_label_2.to_string(),
15063                    detail: Some(multiline_detail_2.to_string()),
15064                    text_edit: gen_text_edit(&params, "new_text_3"),
15065                    ..lsp::CompletionItem::default()
15066                },
15067                lsp::CompletionItem {
15068                    label: "Label with many     spaces and \t but without newlines".to_string(),
15069                    detail: Some(
15070                        "Details with many     spaces and \t but without newlines".to_string(),
15071                    ),
15072                    text_edit: gen_text_edit(&params, "new_text_4"),
15073                    ..lsp::CompletionItem::default()
15074                },
15075            ])))
15076        },
15077    );
15078
15079    editor.update_in(cx, |editor, window, cx| {
15080        cx.focus_self(window);
15081        editor.move_to_end(&MoveToEnd, window, cx);
15082        editor.handle_input(".", window, cx);
15083    });
15084    cx.run_until_parked();
15085    completion_handle.next().await.unwrap();
15086
15087    editor.update(cx, |editor, _| {
15088        assert!(editor.context_menu_visible());
15089        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15090        {
15091            let completion_labels = menu
15092                .completions
15093                .borrow()
15094                .iter()
15095                .map(|c| c.label.text.clone())
15096                .collect::<Vec<_>>();
15097            assert_eq!(
15098                completion_labels,
15099                &[
15100                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
15101                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
15102                    "single line label 2 d e f ",
15103                    "a b c g h i ",
15104                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
15105                ],
15106                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
15107            );
15108
15109            for completion in menu
15110                .completions
15111                .borrow()
15112                .iter() {
15113                    assert_eq!(
15114                        completion.label.filter_range,
15115                        0..completion.label.text.len(),
15116                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
15117                    );
15118                }
15119        } else {
15120            panic!("expected completion menu to be open");
15121        }
15122    });
15123}
15124
15125#[gpui::test]
15126async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
15127    init_test(cx, |_| {});
15128    let mut cx = EditorLspTestContext::new_rust(
15129        lsp::ServerCapabilities {
15130            completion_provider: Some(lsp::CompletionOptions {
15131                trigger_characters: Some(vec![".".to_string()]),
15132                ..Default::default()
15133            }),
15134            ..Default::default()
15135        },
15136        cx,
15137    )
15138    .await;
15139    cx.lsp
15140        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15141            Ok(Some(lsp::CompletionResponse::Array(vec![
15142                lsp::CompletionItem {
15143                    label: "first".into(),
15144                    ..Default::default()
15145                },
15146                lsp::CompletionItem {
15147                    label: "last".into(),
15148                    ..Default::default()
15149                },
15150            ])))
15151        });
15152    cx.set_state("variableˇ");
15153    cx.simulate_keystroke(".");
15154    cx.executor().run_until_parked();
15155
15156    cx.update_editor(|editor, _, _| {
15157        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15158        {
15159            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
15160        } else {
15161            panic!("expected completion menu to be open");
15162        }
15163    });
15164
15165    cx.update_editor(|editor, window, cx| {
15166        editor.move_page_down(&MovePageDown::default(), window, cx);
15167        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15168        {
15169            assert!(
15170                menu.selected_item == 1,
15171                "expected PageDown to select the last item from the context menu"
15172            );
15173        } else {
15174            panic!("expected completion menu to stay open after PageDown");
15175        }
15176    });
15177
15178    cx.update_editor(|editor, window, cx| {
15179        editor.move_page_up(&MovePageUp::default(), window, cx);
15180        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15181        {
15182            assert!(
15183                menu.selected_item == 0,
15184                "expected PageUp to select the first item from the context menu"
15185            );
15186        } else {
15187            panic!("expected completion menu to stay open after PageUp");
15188        }
15189    });
15190}
15191
15192#[gpui::test]
15193async fn test_as_is_completions(cx: &mut TestAppContext) {
15194    init_test(cx, |_| {});
15195    let mut cx = EditorLspTestContext::new_rust(
15196        lsp::ServerCapabilities {
15197            completion_provider: Some(lsp::CompletionOptions {
15198                ..Default::default()
15199            }),
15200            ..Default::default()
15201        },
15202        cx,
15203    )
15204    .await;
15205    cx.lsp
15206        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15207            Ok(Some(lsp::CompletionResponse::Array(vec![
15208                lsp::CompletionItem {
15209                    label: "unsafe".into(),
15210                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15211                        range: lsp::Range {
15212                            start: lsp::Position {
15213                                line: 1,
15214                                character: 2,
15215                            },
15216                            end: lsp::Position {
15217                                line: 1,
15218                                character: 3,
15219                            },
15220                        },
15221                        new_text: "unsafe".to_string(),
15222                    })),
15223                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
15224                    ..Default::default()
15225                },
15226            ])))
15227        });
15228    cx.set_state("fn a() {}\n");
15229    cx.executor().run_until_parked();
15230    cx.update_editor(|editor, window, cx| {
15231        editor.show_completions(
15232            &ShowCompletions {
15233                trigger: Some("\n".into()),
15234            },
15235            window,
15236            cx,
15237        );
15238    });
15239    cx.executor().run_until_parked();
15240
15241    cx.update_editor(|editor, window, cx| {
15242        editor.confirm_completion(&Default::default(), window, cx)
15243    });
15244    cx.executor().run_until_parked();
15245    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
15246}
15247
15248#[gpui::test]
15249async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
15250    init_test(cx, |_| {});
15251    let language =
15252        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
15253    let mut cx = EditorLspTestContext::new(
15254        language,
15255        lsp::ServerCapabilities {
15256            completion_provider: Some(lsp::CompletionOptions {
15257                ..lsp::CompletionOptions::default()
15258            }),
15259            ..lsp::ServerCapabilities::default()
15260        },
15261        cx,
15262    )
15263    .await;
15264
15265    cx.set_state(
15266        "#ifndef BAR_H
15267#define BAR_H
15268
15269#include <stdbool.h>
15270
15271int fn_branch(bool do_branch1, bool do_branch2);
15272
15273#endif // BAR_H
15274ˇ",
15275    );
15276    cx.executor().run_until_parked();
15277    cx.update_editor(|editor, window, cx| {
15278        editor.handle_input("#", window, cx);
15279    });
15280    cx.executor().run_until_parked();
15281    cx.update_editor(|editor, window, cx| {
15282        editor.handle_input("i", window, cx);
15283    });
15284    cx.executor().run_until_parked();
15285    cx.update_editor(|editor, window, cx| {
15286        editor.handle_input("n", window, cx);
15287    });
15288    cx.executor().run_until_parked();
15289    cx.assert_editor_state(
15290        "#ifndef BAR_H
15291#define BAR_H
15292
15293#include <stdbool.h>
15294
15295int fn_branch(bool do_branch1, bool do_branch2);
15296
15297#endif // BAR_H
15298#inˇ",
15299    );
15300
15301    cx.lsp
15302        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15303            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15304                is_incomplete: false,
15305                item_defaults: None,
15306                items: vec![lsp::CompletionItem {
15307                    kind: Some(lsp::CompletionItemKind::SNIPPET),
15308                    label_details: Some(lsp::CompletionItemLabelDetails {
15309                        detail: Some("header".to_string()),
15310                        description: None,
15311                    }),
15312                    label: " include".to_string(),
15313                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15314                        range: lsp::Range {
15315                            start: lsp::Position {
15316                                line: 8,
15317                                character: 1,
15318                            },
15319                            end: lsp::Position {
15320                                line: 8,
15321                                character: 1,
15322                            },
15323                        },
15324                        new_text: "include \"$0\"".to_string(),
15325                    })),
15326                    sort_text: Some("40b67681include".to_string()),
15327                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15328                    filter_text: Some("include".to_string()),
15329                    insert_text: Some("include \"$0\"".to_string()),
15330                    ..lsp::CompletionItem::default()
15331                }],
15332            })))
15333        });
15334    cx.update_editor(|editor, window, cx| {
15335        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15336    });
15337    cx.executor().run_until_parked();
15338    cx.update_editor(|editor, window, cx| {
15339        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15340    });
15341    cx.executor().run_until_parked();
15342    cx.assert_editor_state(
15343        "#ifndef BAR_H
15344#define BAR_H
15345
15346#include <stdbool.h>
15347
15348int fn_branch(bool do_branch1, bool do_branch2);
15349
15350#endif // BAR_H
15351#include \"ˇ\"",
15352    );
15353
15354    cx.lsp
15355        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15356            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15357                is_incomplete: true,
15358                item_defaults: None,
15359                items: vec![lsp::CompletionItem {
15360                    kind: Some(lsp::CompletionItemKind::FILE),
15361                    label: "AGL/".to_string(),
15362                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15363                        range: lsp::Range {
15364                            start: lsp::Position {
15365                                line: 8,
15366                                character: 10,
15367                            },
15368                            end: lsp::Position {
15369                                line: 8,
15370                                character: 11,
15371                            },
15372                        },
15373                        new_text: "AGL/".to_string(),
15374                    })),
15375                    sort_text: Some("40b67681AGL/".to_string()),
15376                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15377                    filter_text: Some("AGL/".to_string()),
15378                    insert_text: Some("AGL/".to_string()),
15379                    ..lsp::CompletionItem::default()
15380                }],
15381            })))
15382        });
15383    cx.update_editor(|editor, window, cx| {
15384        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15385    });
15386    cx.executor().run_until_parked();
15387    cx.update_editor(|editor, window, cx| {
15388        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15389    });
15390    cx.executor().run_until_parked();
15391    cx.assert_editor_state(
15392        r##"#ifndef BAR_H
15393#define BAR_H
15394
15395#include <stdbool.h>
15396
15397int fn_branch(bool do_branch1, bool do_branch2);
15398
15399#endif // BAR_H
15400#include "AGL/ˇ"##,
15401    );
15402
15403    cx.update_editor(|editor, window, cx| {
15404        editor.handle_input("\"", window, cx);
15405    });
15406    cx.executor().run_until_parked();
15407    cx.assert_editor_state(
15408        r##"#ifndef BAR_H
15409#define BAR_H
15410
15411#include <stdbool.h>
15412
15413int fn_branch(bool do_branch1, bool do_branch2);
15414
15415#endif // BAR_H
15416#include "AGL/"ˇ"##,
15417    );
15418}
15419
15420#[gpui::test]
15421async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
15422    init_test(cx, |_| {});
15423
15424    let mut cx = EditorLspTestContext::new_rust(
15425        lsp::ServerCapabilities {
15426            completion_provider: Some(lsp::CompletionOptions {
15427                trigger_characters: Some(vec![".".to_string()]),
15428                resolve_provider: Some(true),
15429                ..Default::default()
15430            }),
15431            ..Default::default()
15432        },
15433        cx,
15434    )
15435    .await;
15436
15437    cx.set_state("fn main() { let a = 2ˇ; }");
15438    cx.simulate_keystroke(".");
15439    let completion_item = lsp::CompletionItem {
15440        label: "Some".into(),
15441        kind: Some(lsp::CompletionItemKind::SNIPPET),
15442        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15443        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15444            kind: lsp::MarkupKind::Markdown,
15445            value: "```rust\nSome(2)\n```".to_string(),
15446        })),
15447        deprecated: Some(false),
15448        sort_text: Some("Some".to_string()),
15449        filter_text: Some("Some".to_string()),
15450        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15451        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15452            range: lsp::Range {
15453                start: lsp::Position {
15454                    line: 0,
15455                    character: 22,
15456                },
15457                end: lsp::Position {
15458                    line: 0,
15459                    character: 22,
15460                },
15461            },
15462            new_text: "Some(2)".to_string(),
15463        })),
15464        additional_text_edits: Some(vec![lsp::TextEdit {
15465            range: lsp::Range {
15466                start: lsp::Position {
15467                    line: 0,
15468                    character: 20,
15469                },
15470                end: lsp::Position {
15471                    line: 0,
15472                    character: 22,
15473                },
15474            },
15475            new_text: "".to_string(),
15476        }]),
15477        ..Default::default()
15478    };
15479
15480    let closure_completion_item = completion_item.clone();
15481    let counter = Arc::new(AtomicUsize::new(0));
15482    let counter_clone = counter.clone();
15483    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15484        let task_completion_item = closure_completion_item.clone();
15485        counter_clone.fetch_add(1, atomic::Ordering::Release);
15486        async move {
15487            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15488                is_incomplete: true,
15489                item_defaults: None,
15490                items: vec![task_completion_item],
15491            })))
15492        }
15493    });
15494
15495    cx.condition(|editor, _| editor.context_menu_visible())
15496        .await;
15497    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
15498    assert!(request.next().await.is_some());
15499    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15500
15501    cx.simulate_keystrokes("S o m");
15502    cx.condition(|editor, _| editor.context_menu_visible())
15503        .await;
15504    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
15505    assert!(request.next().await.is_some());
15506    assert!(request.next().await.is_some());
15507    assert!(request.next().await.is_some());
15508    request.close();
15509    assert!(request.next().await.is_none());
15510    assert_eq!(
15511        counter.load(atomic::Ordering::Acquire),
15512        4,
15513        "With the completions menu open, only one LSP request should happen per input"
15514    );
15515}
15516
15517#[gpui::test]
15518async fn test_toggle_comment(cx: &mut TestAppContext) {
15519    init_test(cx, |_| {});
15520    let mut cx = EditorTestContext::new(cx).await;
15521    let language = Arc::new(Language::new(
15522        LanguageConfig {
15523            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15524            ..Default::default()
15525        },
15526        Some(tree_sitter_rust::LANGUAGE.into()),
15527    ));
15528    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15529
15530    // If multiple selections intersect a line, the line is only toggled once.
15531    cx.set_state(indoc! {"
15532        fn a() {
15533            «//b();
15534            ˇ»// «c();
15535            //ˇ»  d();
15536        }
15537    "});
15538
15539    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15540
15541    cx.assert_editor_state(indoc! {"
15542        fn a() {
15543            «b();
15544            c();
15545            ˇ» d();
15546        }
15547    "});
15548
15549    // The comment prefix is inserted at the same column for every line in a
15550    // selection.
15551    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15552
15553    cx.assert_editor_state(indoc! {"
15554        fn a() {
15555            // «b();
15556            // c();
15557            ˇ»//  d();
15558        }
15559    "});
15560
15561    // If a selection ends at the beginning of a line, that line is not toggled.
15562    cx.set_selections_state(indoc! {"
15563        fn a() {
15564            // b();
15565            «// c();
15566        ˇ»    //  d();
15567        }
15568    "});
15569
15570    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15571
15572    cx.assert_editor_state(indoc! {"
15573        fn a() {
15574            // b();
15575            «c();
15576        ˇ»    //  d();
15577        }
15578    "});
15579
15580    // If a selection span a single line and is empty, the line is toggled.
15581    cx.set_state(indoc! {"
15582        fn a() {
15583            a();
15584            b();
15585        ˇ
15586        }
15587    "});
15588
15589    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15590
15591    cx.assert_editor_state(indoc! {"
15592        fn a() {
15593            a();
15594            b();
15595        //•ˇ
15596        }
15597    "});
15598
15599    // If a selection span multiple lines, empty lines are not toggled.
15600    cx.set_state(indoc! {"
15601        fn a() {
15602            «a();
15603
15604            c();ˇ»
15605        }
15606    "});
15607
15608    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15609
15610    cx.assert_editor_state(indoc! {"
15611        fn a() {
15612            // «a();
15613
15614            // c();ˇ»
15615        }
15616    "});
15617
15618    // If a selection includes multiple comment prefixes, all lines are uncommented.
15619    cx.set_state(indoc! {"
15620        fn a() {
15621            «// a();
15622            /// b();
15623            //! c();ˇ»
15624        }
15625    "});
15626
15627    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15628
15629    cx.assert_editor_state(indoc! {"
15630        fn a() {
15631            «a();
15632            b();
15633            c();ˇ»
15634        }
15635    "});
15636}
15637
15638#[gpui::test]
15639async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
15640    init_test(cx, |_| {});
15641    let mut cx = EditorTestContext::new(cx).await;
15642    let language = Arc::new(Language::new(
15643        LanguageConfig {
15644            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15645            ..Default::default()
15646        },
15647        Some(tree_sitter_rust::LANGUAGE.into()),
15648    ));
15649    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15650
15651    let toggle_comments = &ToggleComments {
15652        advance_downwards: false,
15653        ignore_indent: true,
15654    };
15655
15656    // If multiple selections intersect a line, the line is only toggled once.
15657    cx.set_state(indoc! {"
15658        fn a() {
15659        //    «b();
15660        //    c();
15661        //    ˇ» d();
15662        }
15663    "});
15664
15665    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15666
15667    cx.assert_editor_state(indoc! {"
15668        fn a() {
15669            «b();
15670            c();
15671            ˇ» d();
15672        }
15673    "});
15674
15675    // The comment prefix is inserted at the beginning of each line
15676    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15677
15678    cx.assert_editor_state(indoc! {"
15679        fn a() {
15680        //    «b();
15681        //    c();
15682        //    ˇ» d();
15683        }
15684    "});
15685
15686    // If a selection ends at the beginning of a line, that line is not toggled.
15687    cx.set_selections_state(indoc! {"
15688        fn a() {
15689        //    b();
15690        //    «c();
15691        ˇ»//     d();
15692        }
15693    "});
15694
15695    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15696
15697    cx.assert_editor_state(indoc! {"
15698        fn a() {
15699        //    b();
15700            «c();
15701        ˇ»//     d();
15702        }
15703    "});
15704
15705    // If a selection span a single line and is empty, the line is toggled.
15706    cx.set_state(indoc! {"
15707        fn a() {
15708            a();
15709            b();
15710        ˇ
15711        }
15712    "});
15713
15714    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15715
15716    cx.assert_editor_state(indoc! {"
15717        fn a() {
15718            a();
15719            b();
15720        //ˇ
15721        }
15722    "});
15723
15724    // If a selection span multiple lines, empty lines are not toggled.
15725    cx.set_state(indoc! {"
15726        fn a() {
15727            «a();
15728
15729            c();ˇ»
15730        }
15731    "});
15732
15733    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15734
15735    cx.assert_editor_state(indoc! {"
15736        fn a() {
15737        //    «a();
15738
15739        //    c();ˇ»
15740        }
15741    "});
15742
15743    // If a selection includes multiple comment prefixes, all lines are uncommented.
15744    cx.set_state(indoc! {"
15745        fn a() {
15746        //    «a();
15747        ///    b();
15748        //!    c();ˇ»
15749        }
15750    "});
15751
15752    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15753
15754    cx.assert_editor_state(indoc! {"
15755        fn a() {
15756            «a();
15757            b();
15758            c();ˇ»
15759        }
15760    "});
15761}
15762
15763#[gpui::test]
15764async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
15765    init_test(cx, |_| {});
15766
15767    let language = Arc::new(Language::new(
15768        LanguageConfig {
15769            line_comments: vec!["// ".into()],
15770            ..Default::default()
15771        },
15772        Some(tree_sitter_rust::LANGUAGE.into()),
15773    ));
15774
15775    let mut cx = EditorTestContext::new(cx).await;
15776
15777    cx.language_registry().add(language.clone());
15778    cx.update_buffer(|buffer, cx| {
15779        buffer.set_language(Some(language), cx);
15780    });
15781
15782    let toggle_comments = &ToggleComments {
15783        advance_downwards: true,
15784        ignore_indent: false,
15785    };
15786
15787    // Single cursor on one line -> advance
15788    // Cursor moves horizontally 3 characters as well on non-blank line
15789    cx.set_state(indoc!(
15790        "fn a() {
15791             ˇdog();
15792             cat();
15793        }"
15794    ));
15795    cx.update_editor(|editor, window, cx| {
15796        editor.toggle_comments(toggle_comments, window, cx);
15797    });
15798    cx.assert_editor_state(indoc!(
15799        "fn a() {
15800             // dog();
15801             catˇ();
15802        }"
15803    ));
15804
15805    // Single selection on one line -> don't advance
15806    cx.set_state(indoc!(
15807        "fn a() {
15808             «dog()ˇ»;
15809             cat();
15810        }"
15811    ));
15812    cx.update_editor(|editor, window, cx| {
15813        editor.toggle_comments(toggle_comments, window, cx);
15814    });
15815    cx.assert_editor_state(indoc!(
15816        "fn a() {
15817             // «dog()ˇ»;
15818             cat();
15819        }"
15820    ));
15821
15822    // Multiple cursors on one line -> advance
15823    cx.set_state(indoc!(
15824        "fn a() {
15825             ˇdˇog();
15826             cat();
15827        }"
15828    ));
15829    cx.update_editor(|editor, window, cx| {
15830        editor.toggle_comments(toggle_comments, window, cx);
15831    });
15832    cx.assert_editor_state(indoc!(
15833        "fn a() {
15834             // dog();
15835             catˇ(ˇ);
15836        }"
15837    ));
15838
15839    // Multiple cursors on one line, with selection -> don't advance
15840    cx.set_state(indoc!(
15841        "fn a() {
15842             ˇdˇog«()ˇ»;
15843             cat();
15844        }"
15845    ));
15846    cx.update_editor(|editor, window, cx| {
15847        editor.toggle_comments(toggle_comments, window, cx);
15848    });
15849    cx.assert_editor_state(indoc!(
15850        "fn a() {
15851             // ˇdˇog«()ˇ»;
15852             cat();
15853        }"
15854    ));
15855
15856    // Single cursor on one line -> advance
15857    // Cursor moves to column 0 on blank line
15858    cx.set_state(indoc!(
15859        "fn a() {
15860             ˇdog();
15861
15862             cat();
15863        }"
15864    ));
15865    cx.update_editor(|editor, window, cx| {
15866        editor.toggle_comments(toggle_comments, window, cx);
15867    });
15868    cx.assert_editor_state(indoc!(
15869        "fn a() {
15870             // dog();
15871        ˇ
15872             cat();
15873        }"
15874    ));
15875
15876    // Single cursor on one line -> advance
15877    // Cursor starts and ends at column 0
15878    cx.set_state(indoc!(
15879        "fn a() {
15880         ˇ    dog();
15881             cat();
15882        }"
15883    ));
15884    cx.update_editor(|editor, window, cx| {
15885        editor.toggle_comments(toggle_comments, window, cx);
15886    });
15887    cx.assert_editor_state(indoc!(
15888        "fn a() {
15889             // dog();
15890         ˇ    cat();
15891        }"
15892    ));
15893}
15894
15895#[gpui::test]
15896async fn test_toggle_block_comment(cx: &mut TestAppContext) {
15897    init_test(cx, |_| {});
15898
15899    let mut cx = EditorTestContext::new(cx).await;
15900
15901    let html_language = Arc::new(
15902        Language::new(
15903            LanguageConfig {
15904                name: "HTML".into(),
15905                block_comment: Some(BlockCommentConfig {
15906                    start: "<!-- ".into(),
15907                    prefix: "".into(),
15908                    end: " -->".into(),
15909                    tab_size: 0,
15910                }),
15911                ..Default::default()
15912            },
15913            Some(tree_sitter_html::LANGUAGE.into()),
15914        )
15915        .with_injection_query(
15916            r#"
15917            (script_element
15918                (raw_text) @injection.content
15919                (#set! injection.language "javascript"))
15920            "#,
15921        )
15922        .unwrap(),
15923    );
15924
15925    let javascript_language = Arc::new(Language::new(
15926        LanguageConfig {
15927            name: "JavaScript".into(),
15928            line_comments: vec!["// ".into()],
15929            ..Default::default()
15930        },
15931        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
15932    ));
15933
15934    cx.language_registry().add(html_language.clone());
15935    cx.language_registry().add(javascript_language);
15936    cx.update_buffer(|buffer, cx| {
15937        buffer.set_language(Some(html_language), cx);
15938    });
15939
15940    // Toggle comments for empty selections
15941    cx.set_state(
15942        &r#"
15943            <p>A</p>ˇ
15944            <p>B</p>ˇ
15945            <p>C</p>ˇ
15946        "#
15947        .unindent(),
15948    );
15949    cx.update_editor(|editor, window, cx| {
15950        editor.toggle_comments(&ToggleComments::default(), window, cx)
15951    });
15952    cx.assert_editor_state(
15953        &r#"
15954            <!-- <p>A</p>ˇ -->
15955            <!-- <p>B</p>ˇ -->
15956            <!-- <p>C</p>ˇ -->
15957        "#
15958        .unindent(),
15959    );
15960    cx.update_editor(|editor, window, cx| {
15961        editor.toggle_comments(&ToggleComments::default(), window, cx)
15962    });
15963    cx.assert_editor_state(
15964        &r#"
15965            <p>A</p>ˇ
15966            <p>B</p>ˇ
15967            <p>C</p>ˇ
15968        "#
15969        .unindent(),
15970    );
15971
15972    // Toggle comments for mixture of empty and non-empty selections, where
15973    // multiple selections occupy a given line.
15974    cx.set_state(
15975        &r#"
15976            <p>A«</p>
15977            <p>ˇ»B</p>ˇ
15978            <p>C«</p>
15979            <p>ˇ»D</p>ˇ
15980        "#
15981        .unindent(),
15982    );
15983
15984    cx.update_editor(|editor, window, cx| {
15985        editor.toggle_comments(&ToggleComments::default(), window, cx)
15986    });
15987    cx.assert_editor_state(
15988        &r#"
15989            <!-- <p>A«</p>
15990            <p>ˇ»B</p>ˇ -->
15991            <!-- <p>C«</p>
15992            <p>ˇ»D</p>ˇ -->
15993        "#
15994        .unindent(),
15995    );
15996    cx.update_editor(|editor, window, cx| {
15997        editor.toggle_comments(&ToggleComments::default(), window, cx)
15998    });
15999    cx.assert_editor_state(
16000        &r#"
16001            <p>A«</p>
16002            <p>ˇ»B</p>ˇ
16003            <p>C«</p>
16004            <p>ˇ»D</p>ˇ
16005        "#
16006        .unindent(),
16007    );
16008
16009    // Toggle comments when different languages are active for different
16010    // selections.
16011    cx.set_state(
16012        &r#"
16013            ˇ<script>
16014                ˇvar x = new Y();
16015            ˇ</script>
16016        "#
16017        .unindent(),
16018    );
16019    cx.executor().run_until_parked();
16020    cx.update_editor(|editor, window, cx| {
16021        editor.toggle_comments(&ToggleComments::default(), window, cx)
16022    });
16023    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
16024    // Uncommenting and commenting from this position brings in even more wrong artifacts.
16025    cx.assert_editor_state(
16026        &r#"
16027            <!-- ˇ<script> -->
16028                // ˇvar x = new Y();
16029            <!-- ˇ</script> -->
16030        "#
16031        .unindent(),
16032    );
16033}
16034
16035#[gpui::test]
16036fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
16037    init_test(cx, |_| {});
16038
16039    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16040    let multibuffer = cx.new(|cx| {
16041        let mut multibuffer = MultiBuffer::new(ReadWrite);
16042        multibuffer.push_excerpts(
16043            buffer.clone(),
16044            [
16045                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
16046                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
16047            ],
16048            cx,
16049        );
16050        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
16051        multibuffer
16052    });
16053
16054    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16055    editor.update_in(cx, |editor, window, cx| {
16056        assert_eq!(editor.text(cx), "aaaa\nbbbb");
16057        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16058            s.select_ranges([
16059                Point::new(0, 0)..Point::new(0, 0),
16060                Point::new(1, 0)..Point::new(1, 0),
16061            ])
16062        });
16063
16064        editor.handle_input("X", window, cx);
16065        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
16066        assert_eq!(
16067            editor.selections.ranges(&editor.display_snapshot(cx)),
16068            [
16069                Point::new(0, 1)..Point::new(0, 1),
16070                Point::new(1, 1)..Point::new(1, 1),
16071            ]
16072        );
16073
16074        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
16075        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16076            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
16077        });
16078        editor.backspace(&Default::default(), window, cx);
16079        assert_eq!(editor.text(cx), "Xa\nbbb");
16080        assert_eq!(
16081            editor.selections.ranges(&editor.display_snapshot(cx)),
16082            [Point::new(1, 0)..Point::new(1, 0)]
16083        );
16084
16085        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16086            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
16087        });
16088        editor.backspace(&Default::default(), window, cx);
16089        assert_eq!(editor.text(cx), "X\nbb");
16090        assert_eq!(
16091            editor.selections.ranges(&editor.display_snapshot(cx)),
16092            [Point::new(0, 1)..Point::new(0, 1)]
16093        );
16094    });
16095}
16096
16097#[gpui::test]
16098fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
16099    init_test(cx, |_| {});
16100
16101    let markers = vec![('[', ']').into(), ('(', ')').into()];
16102    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
16103        indoc! {"
16104            [aaaa
16105            (bbbb]
16106            cccc)",
16107        },
16108        markers.clone(),
16109    );
16110    let excerpt_ranges = markers.into_iter().map(|marker| {
16111        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
16112        ExcerptRange::new(context)
16113    });
16114    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
16115    let multibuffer = cx.new(|cx| {
16116        let mut multibuffer = MultiBuffer::new(ReadWrite);
16117        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
16118        multibuffer
16119    });
16120
16121    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16122    editor.update_in(cx, |editor, window, cx| {
16123        let (expected_text, selection_ranges) = marked_text_ranges(
16124            indoc! {"
16125                aaaa
16126                bˇbbb
16127                bˇbbˇb
16128                cccc"
16129            },
16130            true,
16131        );
16132        assert_eq!(editor.text(cx), expected_text);
16133        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16134            s.select_ranges(selection_ranges)
16135        });
16136
16137        editor.handle_input("X", window, cx);
16138
16139        let (expected_text, expected_selections) = marked_text_ranges(
16140            indoc! {"
16141                aaaa
16142                bXˇbbXb
16143                bXˇbbXˇb
16144                cccc"
16145            },
16146            false,
16147        );
16148        assert_eq!(editor.text(cx), expected_text);
16149        assert_eq!(
16150            editor.selections.ranges(&editor.display_snapshot(cx)),
16151            expected_selections
16152        );
16153
16154        editor.newline(&Newline, window, cx);
16155        let (expected_text, expected_selections) = marked_text_ranges(
16156            indoc! {"
16157                aaaa
16158                bX
16159                ˇbbX
16160                b
16161                bX
16162                ˇbbX
16163                ˇb
16164                cccc"
16165            },
16166            false,
16167        );
16168        assert_eq!(editor.text(cx), expected_text);
16169        assert_eq!(
16170            editor.selections.ranges(&editor.display_snapshot(cx)),
16171            expected_selections
16172        );
16173    });
16174}
16175
16176#[gpui::test]
16177fn test_refresh_selections(cx: &mut TestAppContext) {
16178    init_test(cx, |_| {});
16179
16180    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16181    let mut excerpt1_id = None;
16182    let multibuffer = cx.new(|cx| {
16183        let mut multibuffer = MultiBuffer::new(ReadWrite);
16184        excerpt1_id = multibuffer
16185            .push_excerpts(
16186                buffer.clone(),
16187                [
16188                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16189                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16190                ],
16191                cx,
16192            )
16193            .into_iter()
16194            .next();
16195        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16196        multibuffer
16197    });
16198
16199    let editor = cx.add_window(|window, cx| {
16200        let mut editor = build_editor(multibuffer.clone(), window, cx);
16201        let snapshot = editor.snapshot(window, cx);
16202        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16203            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
16204        });
16205        editor.begin_selection(
16206            Point::new(2, 1).to_display_point(&snapshot),
16207            true,
16208            1,
16209            window,
16210            cx,
16211        );
16212        assert_eq!(
16213            editor.selections.ranges(&editor.display_snapshot(cx)),
16214            [
16215                Point::new(1, 3)..Point::new(1, 3),
16216                Point::new(2, 1)..Point::new(2, 1),
16217            ]
16218        );
16219        editor
16220    });
16221
16222    // Refreshing selections is a no-op when excerpts haven't changed.
16223    _ = editor.update(cx, |editor, window, cx| {
16224        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16225        assert_eq!(
16226            editor.selections.ranges(&editor.display_snapshot(cx)),
16227            [
16228                Point::new(1, 3)..Point::new(1, 3),
16229                Point::new(2, 1)..Point::new(2, 1),
16230            ]
16231        );
16232    });
16233
16234    multibuffer.update(cx, |multibuffer, cx| {
16235        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16236    });
16237    _ = editor.update(cx, |editor, window, cx| {
16238        // Removing an excerpt causes the first selection to become degenerate.
16239        assert_eq!(
16240            editor.selections.ranges(&editor.display_snapshot(cx)),
16241            [
16242                Point::new(0, 0)..Point::new(0, 0),
16243                Point::new(0, 1)..Point::new(0, 1)
16244            ]
16245        );
16246
16247        // Refreshing selections will relocate the first selection to the original buffer
16248        // location.
16249        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16250        assert_eq!(
16251            editor.selections.ranges(&editor.display_snapshot(cx)),
16252            [
16253                Point::new(0, 1)..Point::new(0, 1),
16254                Point::new(0, 3)..Point::new(0, 3)
16255            ]
16256        );
16257        assert!(editor.selections.pending_anchor().is_some());
16258    });
16259}
16260
16261#[gpui::test]
16262fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
16263    init_test(cx, |_| {});
16264
16265    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16266    let mut excerpt1_id = None;
16267    let multibuffer = cx.new(|cx| {
16268        let mut multibuffer = MultiBuffer::new(ReadWrite);
16269        excerpt1_id = multibuffer
16270            .push_excerpts(
16271                buffer.clone(),
16272                [
16273                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16274                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16275                ],
16276                cx,
16277            )
16278            .into_iter()
16279            .next();
16280        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16281        multibuffer
16282    });
16283
16284    let editor = cx.add_window(|window, cx| {
16285        let mut editor = build_editor(multibuffer.clone(), window, cx);
16286        let snapshot = editor.snapshot(window, cx);
16287        editor.begin_selection(
16288            Point::new(1, 3).to_display_point(&snapshot),
16289            false,
16290            1,
16291            window,
16292            cx,
16293        );
16294        assert_eq!(
16295            editor.selections.ranges(&editor.display_snapshot(cx)),
16296            [Point::new(1, 3)..Point::new(1, 3)]
16297        );
16298        editor
16299    });
16300
16301    multibuffer.update(cx, |multibuffer, cx| {
16302        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16303    });
16304    _ = editor.update(cx, |editor, window, cx| {
16305        assert_eq!(
16306            editor.selections.ranges(&editor.display_snapshot(cx)),
16307            [Point::new(0, 0)..Point::new(0, 0)]
16308        );
16309
16310        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
16311        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16312        assert_eq!(
16313            editor.selections.ranges(&editor.display_snapshot(cx)),
16314            [Point::new(0, 3)..Point::new(0, 3)]
16315        );
16316        assert!(editor.selections.pending_anchor().is_some());
16317    });
16318}
16319
16320#[gpui::test]
16321async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
16322    init_test(cx, |_| {});
16323
16324    let language = Arc::new(
16325        Language::new(
16326            LanguageConfig {
16327                brackets: BracketPairConfig {
16328                    pairs: vec![
16329                        BracketPair {
16330                            start: "{".to_string(),
16331                            end: "}".to_string(),
16332                            close: true,
16333                            surround: true,
16334                            newline: true,
16335                        },
16336                        BracketPair {
16337                            start: "/* ".to_string(),
16338                            end: " */".to_string(),
16339                            close: true,
16340                            surround: true,
16341                            newline: true,
16342                        },
16343                    ],
16344                    ..Default::default()
16345                },
16346                ..Default::default()
16347            },
16348            Some(tree_sitter_rust::LANGUAGE.into()),
16349        )
16350        .with_indents_query("")
16351        .unwrap(),
16352    );
16353
16354    let text = concat!(
16355        "{   }\n",     //
16356        "  x\n",       //
16357        "  /*   */\n", //
16358        "x\n",         //
16359        "{{} }\n",     //
16360    );
16361
16362    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
16363    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16364    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16365    editor
16366        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
16367        .await;
16368
16369    editor.update_in(cx, |editor, window, cx| {
16370        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16371            s.select_display_ranges([
16372                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
16373                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
16374                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
16375            ])
16376        });
16377        editor.newline(&Newline, window, cx);
16378
16379        assert_eq!(
16380            editor.buffer().read(cx).read(cx).text(),
16381            concat!(
16382                "{ \n",    // Suppress rustfmt
16383                "\n",      //
16384                "}\n",     //
16385                "  x\n",   //
16386                "  /* \n", //
16387                "  \n",    //
16388                "  */\n",  //
16389                "x\n",     //
16390                "{{} \n",  //
16391                "}\n",     //
16392            )
16393        );
16394    });
16395}
16396
16397#[gpui::test]
16398fn test_highlighted_ranges(cx: &mut TestAppContext) {
16399    init_test(cx, |_| {});
16400
16401    let editor = cx.add_window(|window, cx| {
16402        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
16403        build_editor(buffer, window, cx)
16404    });
16405
16406    _ = editor.update(cx, |editor, window, cx| {
16407        struct Type1;
16408        struct Type2;
16409
16410        let buffer = editor.buffer.read(cx).snapshot(cx);
16411
16412        let anchor_range =
16413            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
16414
16415        editor.highlight_background::<Type1>(
16416            &[
16417                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
16418                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
16419                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
16420                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
16421            ],
16422            |_| Hsla::red(),
16423            cx,
16424        );
16425        editor.highlight_background::<Type2>(
16426            &[
16427                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
16428                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
16429                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
16430                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
16431            ],
16432            |_| Hsla::green(),
16433            cx,
16434        );
16435
16436        let snapshot = editor.snapshot(window, cx);
16437        let highlighted_ranges = editor.sorted_background_highlights_in_range(
16438            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
16439            &snapshot,
16440            cx.theme(),
16441        );
16442        assert_eq!(
16443            highlighted_ranges,
16444            &[
16445                (
16446                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
16447                    Hsla::green(),
16448                ),
16449                (
16450                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
16451                    Hsla::red(),
16452                ),
16453                (
16454                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
16455                    Hsla::green(),
16456                ),
16457                (
16458                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16459                    Hsla::red(),
16460                ),
16461            ]
16462        );
16463        assert_eq!(
16464            editor.sorted_background_highlights_in_range(
16465                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
16466                &snapshot,
16467                cx.theme(),
16468            ),
16469            &[(
16470                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16471                Hsla::red(),
16472            )]
16473        );
16474    });
16475}
16476
16477#[gpui::test]
16478async fn test_following(cx: &mut TestAppContext) {
16479    init_test(cx, |_| {});
16480
16481    let fs = FakeFs::new(cx.executor());
16482    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16483
16484    let buffer = project.update(cx, |project, cx| {
16485        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
16486        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
16487    });
16488    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
16489    let follower = cx.update(|cx| {
16490        cx.open_window(
16491            WindowOptions {
16492                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
16493                    gpui::Point::new(px(0.), px(0.)),
16494                    gpui::Point::new(px(10.), px(80.)),
16495                ))),
16496                ..Default::default()
16497            },
16498            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
16499        )
16500        .unwrap()
16501    });
16502
16503    let is_still_following = Rc::new(RefCell::new(true));
16504    let follower_edit_event_count = Rc::new(RefCell::new(0));
16505    let pending_update = Rc::new(RefCell::new(None));
16506    let leader_entity = leader.root(cx).unwrap();
16507    let follower_entity = follower.root(cx).unwrap();
16508    _ = follower.update(cx, {
16509        let update = pending_update.clone();
16510        let is_still_following = is_still_following.clone();
16511        let follower_edit_event_count = follower_edit_event_count.clone();
16512        |_, window, cx| {
16513            cx.subscribe_in(
16514                &leader_entity,
16515                window,
16516                move |_, leader, event, window, cx| {
16517                    leader.read(cx).add_event_to_update_proto(
16518                        event,
16519                        &mut update.borrow_mut(),
16520                        window,
16521                        cx,
16522                    );
16523                },
16524            )
16525            .detach();
16526
16527            cx.subscribe_in(
16528                &follower_entity,
16529                window,
16530                move |_, _, event: &EditorEvent, _window, _cx| {
16531                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
16532                        *is_still_following.borrow_mut() = false;
16533                    }
16534
16535                    if let EditorEvent::BufferEdited = event {
16536                        *follower_edit_event_count.borrow_mut() += 1;
16537                    }
16538                },
16539            )
16540            .detach();
16541        }
16542    });
16543
16544    // Update the selections only
16545    _ = leader.update(cx, |leader, window, cx| {
16546        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16547            s.select_ranges([1..1])
16548        });
16549    });
16550    follower
16551        .update(cx, |follower, window, cx| {
16552            follower.apply_update_proto(
16553                &project,
16554                pending_update.borrow_mut().take().unwrap(),
16555                window,
16556                cx,
16557            )
16558        })
16559        .unwrap()
16560        .await
16561        .unwrap();
16562    _ = follower.update(cx, |follower, _, cx| {
16563        assert_eq!(
16564            follower.selections.ranges(&follower.display_snapshot(cx)),
16565            vec![1..1]
16566        );
16567    });
16568    assert!(*is_still_following.borrow());
16569    assert_eq!(*follower_edit_event_count.borrow(), 0);
16570
16571    // Update the scroll position only
16572    _ = leader.update(cx, |leader, window, cx| {
16573        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16574    });
16575    follower
16576        .update(cx, |follower, window, cx| {
16577            follower.apply_update_proto(
16578                &project,
16579                pending_update.borrow_mut().take().unwrap(),
16580                window,
16581                cx,
16582            )
16583        })
16584        .unwrap()
16585        .await
16586        .unwrap();
16587    assert_eq!(
16588        follower
16589            .update(cx, |follower, _, cx| follower.scroll_position(cx))
16590            .unwrap(),
16591        gpui::Point::new(1.5, 3.5)
16592    );
16593    assert!(*is_still_following.borrow());
16594    assert_eq!(*follower_edit_event_count.borrow(), 0);
16595
16596    // Update the selections and scroll position. The follower's scroll position is updated
16597    // via autoscroll, not via the leader's exact scroll position.
16598    _ = leader.update(cx, |leader, window, cx| {
16599        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16600            s.select_ranges([0..0])
16601        });
16602        leader.request_autoscroll(Autoscroll::newest(), cx);
16603        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16604    });
16605    follower
16606        .update(cx, |follower, window, cx| {
16607            follower.apply_update_proto(
16608                &project,
16609                pending_update.borrow_mut().take().unwrap(),
16610                window,
16611                cx,
16612            )
16613        })
16614        .unwrap()
16615        .await
16616        .unwrap();
16617    _ = follower.update(cx, |follower, _, cx| {
16618        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
16619        assert_eq!(
16620            follower.selections.ranges(&follower.display_snapshot(cx)),
16621            vec![0..0]
16622        );
16623    });
16624    assert!(*is_still_following.borrow());
16625
16626    // Creating a pending selection that precedes another selection
16627    _ = leader.update(cx, |leader, window, cx| {
16628        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16629            s.select_ranges([1..1])
16630        });
16631        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
16632    });
16633    follower
16634        .update(cx, |follower, window, cx| {
16635            follower.apply_update_proto(
16636                &project,
16637                pending_update.borrow_mut().take().unwrap(),
16638                window,
16639                cx,
16640            )
16641        })
16642        .unwrap()
16643        .await
16644        .unwrap();
16645    _ = follower.update(cx, |follower, _, cx| {
16646        assert_eq!(
16647            follower.selections.ranges(&follower.display_snapshot(cx)),
16648            vec![0..0, 1..1]
16649        );
16650    });
16651    assert!(*is_still_following.borrow());
16652
16653    // Extend the pending selection so that it surrounds another selection
16654    _ = leader.update(cx, |leader, window, cx| {
16655        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
16656    });
16657    follower
16658        .update(cx, |follower, window, cx| {
16659            follower.apply_update_proto(
16660                &project,
16661                pending_update.borrow_mut().take().unwrap(),
16662                window,
16663                cx,
16664            )
16665        })
16666        .unwrap()
16667        .await
16668        .unwrap();
16669    _ = follower.update(cx, |follower, _, cx| {
16670        assert_eq!(
16671            follower.selections.ranges(&follower.display_snapshot(cx)),
16672            vec![0..2]
16673        );
16674    });
16675
16676    // Scrolling locally breaks the follow
16677    _ = follower.update(cx, |follower, window, cx| {
16678        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
16679        follower.set_scroll_anchor(
16680            ScrollAnchor {
16681                anchor: top_anchor,
16682                offset: gpui::Point::new(0.0, 0.5),
16683            },
16684            window,
16685            cx,
16686        );
16687    });
16688    assert!(!(*is_still_following.borrow()));
16689}
16690
16691#[gpui::test]
16692async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
16693    init_test(cx, |_| {});
16694
16695    let fs = FakeFs::new(cx.executor());
16696    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16697    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16698    let pane = workspace
16699        .update(cx, |workspace, _, _| workspace.active_pane().clone())
16700        .unwrap();
16701
16702    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16703
16704    let leader = pane.update_in(cx, |_, window, cx| {
16705        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
16706        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
16707    });
16708
16709    // Start following the editor when it has no excerpts.
16710    let mut state_message =
16711        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16712    let workspace_entity = workspace.root(cx).unwrap();
16713    let follower_1 = cx
16714        .update_window(*workspace.deref(), |_, window, cx| {
16715            Editor::from_state_proto(
16716                workspace_entity,
16717                ViewId {
16718                    creator: CollaboratorId::PeerId(PeerId::default()),
16719                    id: 0,
16720                },
16721                &mut state_message,
16722                window,
16723                cx,
16724            )
16725        })
16726        .unwrap()
16727        .unwrap()
16728        .await
16729        .unwrap();
16730
16731    let update_message = Rc::new(RefCell::new(None));
16732    follower_1.update_in(cx, {
16733        let update = update_message.clone();
16734        |_, window, cx| {
16735            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
16736                leader.read(cx).add_event_to_update_proto(
16737                    event,
16738                    &mut update.borrow_mut(),
16739                    window,
16740                    cx,
16741                );
16742            })
16743            .detach();
16744        }
16745    });
16746
16747    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
16748        (
16749            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
16750            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
16751        )
16752    });
16753
16754    // Insert some excerpts.
16755    leader.update(cx, |leader, cx| {
16756        leader.buffer.update(cx, |multibuffer, cx| {
16757            multibuffer.set_excerpts_for_path(
16758                PathKey::with_sort_prefix(1, rel_path("b.txt").into_arc()),
16759                buffer_1.clone(),
16760                vec![
16761                    Point::row_range(0..3),
16762                    Point::row_range(1..6),
16763                    Point::row_range(12..15),
16764                ],
16765                0,
16766                cx,
16767            );
16768            multibuffer.set_excerpts_for_path(
16769                PathKey::with_sort_prefix(1, rel_path("a.txt").into_arc()),
16770                buffer_2.clone(),
16771                vec![Point::row_range(0..6), Point::row_range(8..12)],
16772                0,
16773                cx,
16774            );
16775        });
16776    });
16777
16778    // Apply the update of adding the excerpts.
16779    follower_1
16780        .update_in(cx, |follower, window, cx| {
16781            follower.apply_update_proto(
16782                &project,
16783                update_message.borrow().clone().unwrap(),
16784                window,
16785                cx,
16786            )
16787        })
16788        .await
16789        .unwrap();
16790    assert_eq!(
16791        follower_1.update(cx, |editor, cx| editor.text(cx)),
16792        leader.update(cx, |editor, cx| editor.text(cx))
16793    );
16794    update_message.borrow_mut().take();
16795
16796    // Start following separately after it already has excerpts.
16797    let mut state_message =
16798        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16799    let workspace_entity = workspace.root(cx).unwrap();
16800    let follower_2 = cx
16801        .update_window(*workspace.deref(), |_, window, cx| {
16802            Editor::from_state_proto(
16803                workspace_entity,
16804                ViewId {
16805                    creator: CollaboratorId::PeerId(PeerId::default()),
16806                    id: 0,
16807                },
16808                &mut state_message,
16809                window,
16810                cx,
16811            )
16812        })
16813        .unwrap()
16814        .unwrap()
16815        .await
16816        .unwrap();
16817    assert_eq!(
16818        follower_2.update(cx, |editor, cx| editor.text(cx)),
16819        leader.update(cx, |editor, cx| editor.text(cx))
16820    );
16821
16822    // Remove some excerpts.
16823    leader.update(cx, |leader, cx| {
16824        leader.buffer.update(cx, |multibuffer, cx| {
16825            let excerpt_ids = multibuffer.excerpt_ids();
16826            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
16827            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
16828        });
16829    });
16830
16831    // Apply the update of removing the excerpts.
16832    follower_1
16833        .update_in(cx, |follower, window, cx| {
16834            follower.apply_update_proto(
16835                &project,
16836                update_message.borrow().clone().unwrap(),
16837                window,
16838                cx,
16839            )
16840        })
16841        .await
16842        .unwrap();
16843    follower_2
16844        .update_in(cx, |follower, window, cx| {
16845            follower.apply_update_proto(
16846                &project,
16847                update_message.borrow().clone().unwrap(),
16848                window,
16849                cx,
16850            )
16851        })
16852        .await
16853        .unwrap();
16854    update_message.borrow_mut().take();
16855    assert_eq!(
16856        follower_1.update(cx, |editor, cx| editor.text(cx)),
16857        leader.update(cx, |editor, cx| editor.text(cx))
16858    );
16859}
16860
16861#[gpui::test]
16862async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16863    init_test(cx, |_| {});
16864
16865    let mut cx = EditorTestContext::new(cx).await;
16866    let lsp_store =
16867        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
16868
16869    cx.set_state(indoc! {"
16870        ˇfn func(abc def: i32) -> u32 {
16871        }
16872    "});
16873
16874    cx.update(|_, cx| {
16875        lsp_store.update(cx, |lsp_store, cx| {
16876            lsp_store
16877                .update_diagnostics(
16878                    LanguageServerId(0),
16879                    lsp::PublishDiagnosticsParams {
16880                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
16881                        version: None,
16882                        diagnostics: vec![
16883                            lsp::Diagnostic {
16884                                range: lsp::Range::new(
16885                                    lsp::Position::new(0, 11),
16886                                    lsp::Position::new(0, 12),
16887                                ),
16888                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16889                                ..Default::default()
16890                            },
16891                            lsp::Diagnostic {
16892                                range: lsp::Range::new(
16893                                    lsp::Position::new(0, 12),
16894                                    lsp::Position::new(0, 15),
16895                                ),
16896                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16897                                ..Default::default()
16898                            },
16899                            lsp::Diagnostic {
16900                                range: lsp::Range::new(
16901                                    lsp::Position::new(0, 25),
16902                                    lsp::Position::new(0, 28),
16903                                ),
16904                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16905                                ..Default::default()
16906                            },
16907                        ],
16908                    },
16909                    None,
16910                    DiagnosticSourceKind::Pushed,
16911                    &[],
16912                    cx,
16913                )
16914                .unwrap()
16915        });
16916    });
16917
16918    executor.run_until_parked();
16919
16920    cx.update_editor(|editor, window, cx| {
16921        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16922    });
16923
16924    cx.assert_editor_state(indoc! {"
16925        fn func(abc def: i32) -> ˇu32 {
16926        }
16927    "});
16928
16929    cx.update_editor(|editor, window, cx| {
16930        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16931    });
16932
16933    cx.assert_editor_state(indoc! {"
16934        fn func(abc ˇdef: i32) -> u32 {
16935        }
16936    "});
16937
16938    cx.update_editor(|editor, window, cx| {
16939        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16940    });
16941
16942    cx.assert_editor_state(indoc! {"
16943        fn func(abcˇ def: i32) -> u32 {
16944        }
16945    "});
16946
16947    cx.update_editor(|editor, window, cx| {
16948        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16949    });
16950
16951    cx.assert_editor_state(indoc! {"
16952        fn func(abc def: i32) -> ˇu32 {
16953        }
16954    "});
16955}
16956
16957#[gpui::test]
16958async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16959    init_test(cx, |_| {});
16960
16961    let mut cx = EditorTestContext::new(cx).await;
16962
16963    let diff_base = r#"
16964        use some::mod;
16965
16966        const A: u32 = 42;
16967
16968        fn main() {
16969            println!("hello");
16970
16971            println!("world");
16972        }
16973        "#
16974    .unindent();
16975
16976    // Edits are modified, removed, modified, added
16977    cx.set_state(
16978        &r#"
16979        use some::modified;
16980
16981        ˇ
16982        fn main() {
16983            println!("hello there");
16984
16985            println!("around the");
16986            println!("world");
16987        }
16988        "#
16989        .unindent(),
16990    );
16991
16992    cx.set_head_text(&diff_base);
16993    executor.run_until_parked();
16994
16995    cx.update_editor(|editor, window, cx| {
16996        //Wrap around the bottom of the buffer
16997        for _ in 0..3 {
16998            editor.go_to_next_hunk(&GoToHunk, window, cx);
16999        }
17000    });
17001
17002    cx.assert_editor_state(
17003        &r#"
17004        ˇuse some::modified;
17005
17006
17007        fn main() {
17008            println!("hello there");
17009
17010            println!("around the");
17011            println!("world");
17012        }
17013        "#
17014        .unindent(),
17015    );
17016
17017    cx.update_editor(|editor, window, cx| {
17018        //Wrap around the top of the buffer
17019        for _ in 0..2 {
17020            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17021        }
17022    });
17023
17024    cx.assert_editor_state(
17025        &r#"
17026        use some::modified;
17027
17028
17029        fn main() {
17030        ˇ    println!("hello there");
17031
17032            println!("around the");
17033            println!("world");
17034        }
17035        "#
17036        .unindent(),
17037    );
17038
17039    cx.update_editor(|editor, window, cx| {
17040        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17041    });
17042
17043    cx.assert_editor_state(
17044        &r#"
17045        use some::modified;
17046
17047        ˇ
17048        fn main() {
17049            println!("hello there");
17050
17051            println!("around the");
17052            println!("world");
17053        }
17054        "#
17055        .unindent(),
17056    );
17057
17058    cx.update_editor(|editor, window, cx| {
17059        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17060    });
17061
17062    cx.assert_editor_state(
17063        &r#"
17064        ˇuse some::modified;
17065
17066
17067        fn main() {
17068            println!("hello there");
17069
17070            println!("around the");
17071            println!("world");
17072        }
17073        "#
17074        .unindent(),
17075    );
17076
17077    cx.update_editor(|editor, window, cx| {
17078        for _ in 0..2 {
17079            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17080        }
17081    });
17082
17083    cx.assert_editor_state(
17084        &r#"
17085        use some::modified;
17086
17087
17088        fn main() {
17089        ˇ    println!("hello there");
17090
17091            println!("around the");
17092            println!("world");
17093        }
17094        "#
17095        .unindent(),
17096    );
17097
17098    cx.update_editor(|editor, window, cx| {
17099        editor.fold(&Fold, window, cx);
17100    });
17101
17102    cx.update_editor(|editor, window, cx| {
17103        editor.go_to_next_hunk(&GoToHunk, window, cx);
17104    });
17105
17106    cx.assert_editor_state(
17107        &r#"
17108        ˇuse some::modified;
17109
17110
17111        fn main() {
17112            println!("hello there");
17113
17114            println!("around the");
17115            println!("world");
17116        }
17117        "#
17118        .unindent(),
17119    );
17120}
17121
17122#[test]
17123fn test_split_words() {
17124    fn split(text: &str) -> Vec<&str> {
17125        split_words(text).collect()
17126    }
17127
17128    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
17129    assert_eq!(split("hello_world"), &["hello_", "world"]);
17130    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
17131    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
17132    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
17133    assert_eq!(split("helloworld"), &["helloworld"]);
17134
17135    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
17136}
17137
17138#[gpui::test]
17139async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
17140    init_test(cx, |_| {});
17141
17142    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
17143    let mut assert = |before, after| {
17144        let _state_context = cx.set_state(before);
17145        cx.run_until_parked();
17146        cx.update_editor(|editor, window, cx| {
17147            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
17148        });
17149        cx.run_until_parked();
17150        cx.assert_editor_state(after);
17151    };
17152
17153    // Outside bracket jumps to outside of matching bracket
17154    assert("console.logˇ(var);", "console.log(var)ˇ;");
17155    assert("console.log(var)ˇ;", "console.logˇ(var);");
17156
17157    // Inside bracket jumps to inside of matching bracket
17158    assert("console.log(ˇvar);", "console.log(varˇ);");
17159    assert("console.log(varˇ);", "console.log(ˇvar);");
17160
17161    // When outside a bracket and inside, favor jumping to the inside bracket
17162    assert(
17163        "console.log('foo', [1, 2, 3]ˇ);",
17164        "console.log(ˇ'foo', [1, 2, 3]);",
17165    );
17166    assert(
17167        "console.log(ˇ'foo', [1, 2, 3]);",
17168        "console.log('foo', [1, 2, 3]ˇ);",
17169    );
17170
17171    // Bias forward if two options are equally likely
17172    assert(
17173        "let result = curried_fun()ˇ();",
17174        "let result = curried_fun()()ˇ;",
17175    );
17176
17177    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
17178    assert(
17179        indoc! {"
17180            function test() {
17181                console.log('test')ˇ
17182            }"},
17183        indoc! {"
17184            function test() {
17185                console.logˇ('test')
17186            }"},
17187    );
17188}
17189
17190#[gpui::test]
17191async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
17192    init_test(cx, |_| {});
17193
17194    let fs = FakeFs::new(cx.executor());
17195    fs.insert_tree(
17196        path!("/a"),
17197        json!({
17198            "main.rs": "fn main() { let a = 5; }",
17199            "other.rs": "// Test file",
17200        }),
17201    )
17202    .await;
17203    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17204
17205    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17206    language_registry.add(Arc::new(Language::new(
17207        LanguageConfig {
17208            name: "Rust".into(),
17209            matcher: LanguageMatcher {
17210                path_suffixes: vec!["rs".to_string()],
17211                ..Default::default()
17212            },
17213            brackets: BracketPairConfig {
17214                pairs: vec![BracketPair {
17215                    start: "{".to_string(),
17216                    end: "}".to_string(),
17217                    close: true,
17218                    surround: true,
17219                    newline: true,
17220                }],
17221                disabled_scopes_by_bracket_ix: Vec::new(),
17222            },
17223            ..Default::default()
17224        },
17225        Some(tree_sitter_rust::LANGUAGE.into()),
17226    )));
17227    let mut fake_servers = language_registry.register_fake_lsp(
17228        "Rust",
17229        FakeLspAdapter {
17230            capabilities: lsp::ServerCapabilities {
17231                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17232                    first_trigger_character: "{".to_string(),
17233                    more_trigger_character: None,
17234                }),
17235                ..Default::default()
17236            },
17237            ..Default::default()
17238        },
17239    );
17240
17241    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17242
17243    let cx = &mut VisualTestContext::from_window(*workspace, cx);
17244
17245    let worktree_id = workspace
17246        .update(cx, |workspace, _, cx| {
17247            workspace.project().update(cx, |project, cx| {
17248                project.worktrees(cx).next().unwrap().read(cx).id()
17249            })
17250        })
17251        .unwrap();
17252
17253    let buffer = project
17254        .update(cx, |project, cx| {
17255            project.open_local_buffer(path!("/a/main.rs"), cx)
17256        })
17257        .await
17258        .unwrap();
17259    let editor_handle = workspace
17260        .update(cx, |workspace, window, cx| {
17261            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
17262        })
17263        .unwrap()
17264        .await
17265        .unwrap()
17266        .downcast::<Editor>()
17267        .unwrap();
17268
17269    cx.executor().start_waiting();
17270    let fake_server = fake_servers.next().await.unwrap();
17271
17272    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
17273        |params, _| async move {
17274            assert_eq!(
17275                params.text_document_position.text_document.uri,
17276                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
17277            );
17278            assert_eq!(
17279                params.text_document_position.position,
17280                lsp::Position::new(0, 21),
17281            );
17282
17283            Ok(Some(vec![lsp::TextEdit {
17284                new_text: "]".to_string(),
17285                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17286            }]))
17287        },
17288    );
17289
17290    editor_handle.update_in(cx, |editor, window, cx| {
17291        window.focus(&editor.focus_handle(cx));
17292        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17293            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
17294        });
17295        editor.handle_input("{", window, cx);
17296    });
17297
17298    cx.executor().run_until_parked();
17299
17300    buffer.update(cx, |buffer, _| {
17301        assert_eq!(
17302            buffer.text(),
17303            "fn main() { let a = {5}; }",
17304            "No extra braces from on type formatting should appear in the buffer"
17305        )
17306    });
17307}
17308
17309#[gpui::test(iterations = 20, seeds(31))]
17310async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
17311    init_test(cx, |_| {});
17312
17313    let mut cx = EditorLspTestContext::new_rust(
17314        lsp::ServerCapabilities {
17315            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17316                first_trigger_character: ".".to_string(),
17317                more_trigger_character: None,
17318            }),
17319            ..Default::default()
17320        },
17321        cx,
17322    )
17323    .await;
17324
17325    cx.update_buffer(|buffer, _| {
17326        // This causes autoindent to be async.
17327        buffer.set_sync_parse_timeout(Duration::ZERO)
17328    });
17329
17330    cx.set_state("fn c() {\n    d()ˇ\n}\n");
17331    cx.simulate_keystroke("\n");
17332    cx.run_until_parked();
17333
17334    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
17335    let mut request =
17336        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
17337            let buffer_cloned = buffer_cloned.clone();
17338            async move {
17339                buffer_cloned.update(&mut cx, |buffer, _| {
17340                    assert_eq!(
17341                        buffer.text(),
17342                        "fn c() {\n    d()\n        .\n}\n",
17343                        "OnTypeFormatting should triggered after autoindent applied"
17344                    )
17345                })?;
17346
17347                Ok(Some(vec![]))
17348            }
17349        });
17350
17351    cx.simulate_keystroke(".");
17352    cx.run_until_parked();
17353
17354    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
17355    assert!(request.next().await.is_some());
17356    request.close();
17357    assert!(request.next().await.is_none());
17358}
17359
17360#[gpui::test]
17361async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
17362    init_test(cx, |_| {});
17363
17364    let fs = FakeFs::new(cx.executor());
17365    fs.insert_tree(
17366        path!("/a"),
17367        json!({
17368            "main.rs": "fn main() { let a = 5; }",
17369            "other.rs": "// Test file",
17370        }),
17371    )
17372    .await;
17373
17374    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17375
17376    let server_restarts = Arc::new(AtomicUsize::new(0));
17377    let closure_restarts = Arc::clone(&server_restarts);
17378    let language_server_name = "test language server";
17379    let language_name: LanguageName = "Rust".into();
17380
17381    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17382    language_registry.add(Arc::new(Language::new(
17383        LanguageConfig {
17384            name: language_name.clone(),
17385            matcher: LanguageMatcher {
17386                path_suffixes: vec!["rs".to_string()],
17387                ..Default::default()
17388            },
17389            ..Default::default()
17390        },
17391        Some(tree_sitter_rust::LANGUAGE.into()),
17392    )));
17393    let mut fake_servers = language_registry.register_fake_lsp(
17394        "Rust",
17395        FakeLspAdapter {
17396            name: language_server_name,
17397            initialization_options: Some(json!({
17398                "testOptionValue": true
17399            })),
17400            initializer: Some(Box::new(move |fake_server| {
17401                let task_restarts = Arc::clone(&closure_restarts);
17402                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
17403                    task_restarts.fetch_add(1, atomic::Ordering::Release);
17404                    futures::future::ready(Ok(()))
17405                });
17406            })),
17407            ..Default::default()
17408        },
17409    );
17410
17411    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17412    let _buffer = project
17413        .update(cx, |project, cx| {
17414            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
17415        })
17416        .await
17417        .unwrap();
17418    let _fake_server = fake_servers.next().await.unwrap();
17419    update_test_language_settings(cx, |language_settings| {
17420        language_settings.languages.0.insert(
17421            language_name.clone().0,
17422            LanguageSettingsContent {
17423                tab_size: NonZeroU32::new(8),
17424                ..Default::default()
17425            },
17426        );
17427    });
17428    cx.executor().run_until_parked();
17429    assert_eq!(
17430        server_restarts.load(atomic::Ordering::Acquire),
17431        0,
17432        "Should not restart LSP server on an unrelated change"
17433    );
17434
17435    update_test_project_settings(cx, |project_settings| {
17436        project_settings.lsp.insert(
17437            "Some other server name".into(),
17438            LspSettings {
17439                binary: None,
17440                settings: None,
17441                initialization_options: Some(json!({
17442                    "some other init value": false
17443                })),
17444                enable_lsp_tasks: false,
17445                fetch: None,
17446            },
17447        );
17448    });
17449    cx.executor().run_until_parked();
17450    assert_eq!(
17451        server_restarts.load(atomic::Ordering::Acquire),
17452        0,
17453        "Should not restart LSP server on an unrelated LSP settings change"
17454    );
17455
17456    update_test_project_settings(cx, |project_settings| {
17457        project_settings.lsp.insert(
17458            language_server_name.into(),
17459            LspSettings {
17460                binary: None,
17461                settings: None,
17462                initialization_options: Some(json!({
17463                    "anotherInitValue": false
17464                })),
17465                enable_lsp_tasks: false,
17466                fetch: None,
17467            },
17468        );
17469    });
17470    cx.executor().run_until_parked();
17471    assert_eq!(
17472        server_restarts.load(atomic::Ordering::Acquire),
17473        1,
17474        "Should restart LSP server on a related LSP settings change"
17475    );
17476
17477    update_test_project_settings(cx, |project_settings| {
17478        project_settings.lsp.insert(
17479            language_server_name.into(),
17480            LspSettings {
17481                binary: None,
17482                settings: None,
17483                initialization_options: Some(json!({
17484                    "anotherInitValue": false
17485                })),
17486                enable_lsp_tasks: false,
17487                fetch: None,
17488            },
17489        );
17490    });
17491    cx.executor().run_until_parked();
17492    assert_eq!(
17493        server_restarts.load(atomic::Ordering::Acquire),
17494        1,
17495        "Should not restart LSP server on a related LSP settings change that is the same"
17496    );
17497
17498    update_test_project_settings(cx, |project_settings| {
17499        project_settings.lsp.insert(
17500            language_server_name.into(),
17501            LspSettings {
17502                binary: None,
17503                settings: None,
17504                initialization_options: None,
17505                enable_lsp_tasks: false,
17506                fetch: None,
17507            },
17508        );
17509    });
17510    cx.executor().run_until_parked();
17511    assert_eq!(
17512        server_restarts.load(atomic::Ordering::Acquire),
17513        2,
17514        "Should restart LSP server on another related LSP settings change"
17515    );
17516}
17517
17518#[gpui::test]
17519async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
17520    init_test(cx, |_| {});
17521
17522    let mut cx = EditorLspTestContext::new_rust(
17523        lsp::ServerCapabilities {
17524            completion_provider: Some(lsp::CompletionOptions {
17525                trigger_characters: Some(vec![".".to_string()]),
17526                resolve_provider: Some(true),
17527                ..Default::default()
17528            }),
17529            ..Default::default()
17530        },
17531        cx,
17532    )
17533    .await;
17534
17535    cx.set_state("fn main() { let a = 2ˇ; }");
17536    cx.simulate_keystroke(".");
17537    let completion_item = lsp::CompletionItem {
17538        label: "some".into(),
17539        kind: Some(lsp::CompletionItemKind::SNIPPET),
17540        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
17541        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
17542            kind: lsp::MarkupKind::Markdown,
17543            value: "```rust\nSome(2)\n```".to_string(),
17544        })),
17545        deprecated: Some(false),
17546        sort_text: Some("fffffff2".to_string()),
17547        filter_text: Some("some".to_string()),
17548        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
17549        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17550            range: lsp::Range {
17551                start: lsp::Position {
17552                    line: 0,
17553                    character: 22,
17554                },
17555                end: lsp::Position {
17556                    line: 0,
17557                    character: 22,
17558                },
17559            },
17560            new_text: "Some(2)".to_string(),
17561        })),
17562        additional_text_edits: Some(vec![lsp::TextEdit {
17563            range: lsp::Range {
17564                start: lsp::Position {
17565                    line: 0,
17566                    character: 20,
17567                },
17568                end: lsp::Position {
17569                    line: 0,
17570                    character: 22,
17571                },
17572            },
17573            new_text: "".to_string(),
17574        }]),
17575        ..Default::default()
17576    };
17577
17578    let closure_completion_item = completion_item.clone();
17579    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17580        let task_completion_item = closure_completion_item.clone();
17581        async move {
17582            Ok(Some(lsp::CompletionResponse::Array(vec![
17583                task_completion_item,
17584            ])))
17585        }
17586    });
17587
17588    request.next().await;
17589
17590    cx.condition(|editor, _| editor.context_menu_visible())
17591        .await;
17592    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
17593        editor
17594            .confirm_completion(&ConfirmCompletion::default(), window, cx)
17595            .unwrap()
17596    });
17597    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
17598
17599    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
17600        let task_completion_item = completion_item.clone();
17601        async move { Ok(task_completion_item) }
17602    })
17603    .next()
17604    .await
17605    .unwrap();
17606    apply_additional_edits.await.unwrap();
17607    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
17608}
17609
17610#[gpui::test]
17611async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
17612    init_test(cx, |_| {});
17613
17614    let mut cx = EditorLspTestContext::new_rust(
17615        lsp::ServerCapabilities {
17616            completion_provider: Some(lsp::CompletionOptions {
17617                trigger_characters: Some(vec![".".to_string()]),
17618                resolve_provider: Some(true),
17619                ..Default::default()
17620            }),
17621            ..Default::default()
17622        },
17623        cx,
17624    )
17625    .await;
17626
17627    cx.set_state("fn main() { let a = 2ˇ; }");
17628    cx.simulate_keystroke(".");
17629
17630    let item1 = lsp::CompletionItem {
17631        label: "method id()".to_string(),
17632        filter_text: Some("id".to_string()),
17633        detail: None,
17634        documentation: None,
17635        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17636            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17637            new_text: ".id".to_string(),
17638        })),
17639        ..lsp::CompletionItem::default()
17640    };
17641
17642    let item2 = lsp::CompletionItem {
17643        label: "other".to_string(),
17644        filter_text: Some("other".to_string()),
17645        detail: None,
17646        documentation: None,
17647        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17648            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17649            new_text: ".other".to_string(),
17650        })),
17651        ..lsp::CompletionItem::default()
17652    };
17653
17654    let item1 = item1.clone();
17655    cx.set_request_handler::<lsp::request::Completion, _, _>({
17656        let item1 = item1.clone();
17657        move |_, _, _| {
17658            let item1 = item1.clone();
17659            let item2 = item2.clone();
17660            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
17661        }
17662    })
17663    .next()
17664    .await;
17665
17666    cx.condition(|editor, _| editor.context_menu_visible())
17667        .await;
17668    cx.update_editor(|editor, _, _| {
17669        let context_menu = editor.context_menu.borrow_mut();
17670        let context_menu = context_menu
17671            .as_ref()
17672            .expect("Should have the context menu deployed");
17673        match context_menu {
17674            CodeContextMenu::Completions(completions_menu) => {
17675                let completions = completions_menu.completions.borrow_mut();
17676                assert_eq!(
17677                    completions
17678                        .iter()
17679                        .map(|completion| &completion.label.text)
17680                        .collect::<Vec<_>>(),
17681                    vec!["method id()", "other"]
17682                )
17683            }
17684            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17685        }
17686    });
17687
17688    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
17689        let item1 = item1.clone();
17690        move |_, item_to_resolve, _| {
17691            let item1 = item1.clone();
17692            async move {
17693                if item1 == item_to_resolve {
17694                    Ok(lsp::CompletionItem {
17695                        label: "method id()".to_string(),
17696                        filter_text: Some("id".to_string()),
17697                        detail: Some("Now resolved!".to_string()),
17698                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
17699                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17700                            range: lsp::Range::new(
17701                                lsp::Position::new(0, 22),
17702                                lsp::Position::new(0, 22),
17703                            ),
17704                            new_text: ".id".to_string(),
17705                        })),
17706                        ..lsp::CompletionItem::default()
17707                    })
17708                } else {
17709                    Ok(item_to_resolve)
17710                }
17711            }
17712        }
17713    })
17714    .next()
17715    .await
17716    .unwrap();
17717    cx.run_until_parked();
17718
17719    cx.update_editor(|editor, window, cx| {
17720        editor.context_menu_next(&Default::default(), window, cx);
17721    });
17722
17723    cx.update_editor(|editor, _, _| {
17724        let context_menu = editor.context_menu.borrow_mut();
17725        let context_menu = context_menu
17726            .as_ref()
17727            .expect("Should have the context menu deployed");
17728        match context_menu {
17729            CodeContextMenu::Completions(completions_menu) => {
17730                let completions = completions_menu.completions.borrow_mut();
17731                assert_eq!(
17732                    completions
17733                        .iter()
17734                        .map(|completion| &completion.label.text)
17735                        .collect::<Vec<_>>(),
17736                    vec!["method id() Now resolved!", "other"],
17737                    "Should update first completion label, but not second as the filter text did not match."
17738                );
17739            }
17740            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17741        }
17742    });
17743}
17744
17745#[gpui::test]
17746async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
17747    init_test(cx, |_| {});
17748    let mut cx = EditorLspTestContext::new_rust(
17749        lsp::ServerCapabilities {
17750            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
17751            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
17752            completion_provider: Some(lsp::CompletionOptions {
17753                resolve_provider: Some(true),
17754                ..Default::default()
17755            }),
17756            ..Default::default()
17757        },
17758        cx,
17759    )
17760    .await;
17761    cx.set_state(indoc! {"
17762        struct TestStruct {
17763            field: i32
17764        }
17765
17766        fn mainˇ() {
17767            let unused_var = 42;
17768            let test_struct = TestStruct { field: 42 };
17769        }
17770    "});
17771    let symbol_range = cx.lsp_range(indoc! {"
17772        struct TestStruct {
17773            field: i32
17774        }
17775
17776        «fn main»() {
17777            let unused_var = 42;
17778            let test_struct = TestStruct { field: 42 };
17779        }
17780    "});
17781    let mut hover_requests =
17782        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
17783            Ok(Some(lsp::Hover {
17784                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
17785                    kind: lsp::MarkupKind::Markdown,
17786                    value: "Function documentation".to_string(),
17787                }),
17788                range: Some(symbol_range),
17789            }))
17790        });
17791
17792    // Case 1: Test that code action menu hide hover popover
17793    cx.dispatch_action(Hover);
17794    hover_requests.next().await;
17795    cx.condition(|editor, _| editor.hover_state.visible()).await;
17796    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
17797        move |_, _, _| async move {
17798            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
17799                lsp::CodeAction {
17800                    title: "Remove unused variable".to_string(),
17801                    kind: Some(CodeActionKind::QUICKFIX),
17802                    edit: Some(lsp::WorkspaceEdit {
17803                        changes: Some(
17804                            [(
17805                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
17806                                vec![lsp::TextEdit {
17807                                    range: lsp::Range::new(
17808                                        lsp::Position::new(5, 4),
17809                                        lsp::Position::new(5, 27),
17810                                    ),
17811                                    new_text: "".to_string(),
17812                                }],
17813                            )]
17814                            .into_iter()
17815                            .collect(),
17816                        ),
17817                        ..Default::default()
17818                    }),
17819                    ..Default::default()
17820                },
17821            )]))
17822        },
17823    );
17824    cx.update_editor(|editor, window, cx| {
17825        editor.toggle_code_actions(
17826            &ToggleCodeActions {
17827                deployed_from: None,
17828                quick_launch: false,
17829            },
17830            window,
17831            cx,
17832        );
17833    });
17834    code_action_requests.next().await;
17835    cx.run_until_parked();
17836    cx.condition(|editor, _| editor.context_menu_visible())
17837        .await;
17838    cx.update_editor(|editor, _, _| {
17839        assert!(
17840            !editor.hover_state.visible(),
17841            "Hover popover should be hidden when code action menu is shown"
17842        );
17843        // Hide code actions
17844        editor.context_menu.take();
17845    });
17846
17847    // Case 2: Test that code completions hide hover popover
17848    cx.dispatch_action(Hover);
17849    hover_requests.next().await;
17850    cx.condition(|editor, _| editor.hover_state.visible()).await;
17851    let counter = Arc::new(AtomicUsize::new(0));
17852    let mut completion_requests =
17853        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17854            let counter = counter.clone();
17855            async move {
17856                counter.fetch_add(1, atomic::Ordering::Release);
17857                Ok(Some(lsp::CompletionResponse::Array(vec![
17858                    lsp::CompletionItem {
17859                        label: "main".into(),
17860                        kind: Some(lsp::CompletionItemKind::FUNCTION),
17861                        detail: Some("() -> ()".to_string()),
17862                        ..Default::default()
17863                    },
17864                    lsp::CompletionItem {
17865                        label: "TestStruct".into(),
17866                        kind: Some(lsp::CompletionItemKind::STRUCT),
17867                        detail: Some("struct TestStruct".to_string()),
17868                        ..Default::default()
17869                    },
17870                ])))
17871            }
17872        });
17873    cx.update_editor(|editor, window, cx| {
17874        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
17875    });
17876    completion_requests.next().await;
17877    cx.condition(|editor, _| editor.context_menu_visible())
17878        .await;
17879    cx.update_editor(|editor, _, _| {
17880        assert!(
17881            !editor.hover_state.visible(),
17882            "Hover popover should be hidden when completion menu is shown"
17883        );
17884    });
17885}
17886
17887#[gpui::test]
17888async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
17889    init_test(cx, |_| {});
17890
17891    let mut cx = EditorLspTestContext::new_rust(
17892        lsp::ServerCapabilities {
17893            completion_provider: Some(lsp::CompletionOptions {
17894                trigger_characters: Some(vec![".".to_string()]),
17895                resolve_provider: Some(true),
17896                ..Default::default()
17897            }),
17898            ..Default::default()
17899        },
17900        cx,
17901    )
17902    .await;
17903
17904    cx.set_state("fn main() { let a = 2ˇ; }");
17905    cx.simulate_keystroke(".");
17906
17907    let unresolved_item_1 = lsp::CompletionItem {
17908        label: "id".to_string(),
17909        filter_text: Some("id".to_string()),
17910        detail: None,
17911        documentation: None,
17912        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17913            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17914            new_text: ".id".to_string(),
17915        })),
17916        ..lsp::CompletionItem::default()
17917    };
17918    let resolved_item_1 = lsp::CompletionItem {
17919        additional_text_edits: Some(vec![lsp::TextEdit {
17920            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17921            new_text: "!!".to_string(),
17922        }]),
17923        ..unresolved_item_1.clone()
17924    };
17925    let unresolved_item_2 = lsp::CompletionItem {
17926        label: "other".to_string(),
17927        filter_text: Some("other".to_string()),
17928        detail: None,
17929        documentation: None,
17930        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17931            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17932            new_text: ".other".to_string(),
17933        })),
17934        ..lsp::CompletionItem::default()
17935    };
17936    let resolved_item_2 = lsp::CompletionItem {
17937        additional_text_edits: Some(vec![lsp::TextEdit {
17938            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17939            new_text: "??".to_string(),
17940        }]),
17941        ..unresolved_item_2.clone()
17942    };
17943
17944    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
17945    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
17946    cx.lsp
17947        .server
17948        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17949            let unresolved_item_1 = unresolved_item_1.clone();
17950            let resolved_item_1 = resolved_item_1.clone();
17951            let unresolved_item_2 = unresolved_item_2.clone();
17952            let resolved_item_2 = resolved_item_2.clone();
17953            let resolve_requests_1 = resolve_requests_1.clone();
17954            let resolve_requests_2 = resolve_requests_2.clone();
17955            move |unresolved_request, _| {
17956                let unresolved_item_1 = unresolved_item_1.clone();
17957                let resolved_item_1 = resolved_item_1.clone();
17958                let unresolved_item_2 = unresolved_item_2.clone();
17959                let resolved_item_2 = resolved_item_2.clone();
17960                let resolve_requests_1 = resolve_requests_1.clone();
17961                let resolve_requests_2 = resolve_requests_2.clone();
17962                async move {
17963                    if unresolved_request == unresolved_item_1 {
17964                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
17965                        Ok(resolved_item_1.clone())
17966                    } else if unresolved_request == unresolved_item_2 {
17967                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
17968                        Ok(resolved_item_2.clone())
17969                    } else {
17970                        panic!("Unexpected completion item {unresolved_request:?}")
17971                    }
17972                }
17973            }
17974        })
17975        .detach();
17976
17977    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17978        let unresolved_item_1 = unresolved_item_1.clone();
17979        let unresolved_item_2 = unresolved_item_2.clone();
17980        async move {
17981            Ok(Some(lsp::CompletionResponse::Array(vec![
17982                unresolved_item_1,
17983                unresolved_item_2,
17984            ])))
17985        }
17986    })
17987    .next()
17988    .await;
17989
17990    cx.condition(|editor, _| editor.context_menu_visible())
17991        .await;
17992    cx.update_editor(|editor, _, _| {
17993        let context_menu = editor.context_menu.borrow_mut();
17994        let context_menu = context_menu
17995            .as_ref()
17996            .expect("Should have the context menu deployed");
17997        match context_menu {
17998            CodeContextMenu::Completions(completions_menu) => {
17999                let completions = completions_menu.completions.borrow_mut();
18000                assert_eq!(
18001                    completions
18002                        .iter()
18003                        .map(|completion| &completion.label.text)
18004                        .collect::<Vec<_>>(),
18005                    vec!["id", "other"]
18006                )
18007            }
18008            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18009        }
18010    });
18011    cx.run_until_parked();
18012
18013    cx.update_editor(|editor, window, cx| {
18014        editor.context_menu_next(&ContextMenuNext, window, cx);
18015    });
18016    cx.run_until_parked();
18017    cx.update_editor(|editor, window, cx| {
18018        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
18019    });
18020    cx.run_until_parked();
18021    cx.update_editor(|editor, window, cx| {
18022        editor.context_menu_next(&ContextMenuNext, window, cx);
18023    });
18024    cx.run_until_parked();
18025    cx.update_editor(|editor, window, cx| {
18026        editor
18027            .compose_completion(&ComposeCompletion::default(), window, cx)
18028            .expect("No task returned")
18029    })
18030    .await
18031    .expect("Completion failed");
18032    cx.run_until_parked();
18033
18034    cx.update_editor(|editor, _, cx| {
18035        assert_eq!(
18036            resolve_requests_1.load(atomic::Ordering::Acquire),
18037            1,
18038            "Should always resolve once despite multiple selections"
18039        );
18040        assert_eq!(
18041            resolve_requests_2.load(atomic::Ordering::Acquire),
18042            1,
18043            "Should always resolve once after multiple selections and applying the completion"
18044        );
18045        assert_eq!(
18046            editor.text(cx),
18047            "fn main() { let a = ??.other; }",
18048            "Should use resolved data when applying the completion"
18049        );
18050    });
18051}
18052
18053#[gpui::test]
18054async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
18055    init_test(cx, |_| {});
18056
18057    let item_0 = lsp::CompletionItem {
18058        label: "abs".into(),
18059        insert_text: Some("abs".into()),
18060        data: Some(json!({ "very": "special"})),
18061        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
18062        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
18063            lsp::InsertReplaceEdit {
18064                new_text: "abs".to_string(),
18065                insert: lsp::Range::default(),
18066                replace: lsp::Range::default(),
18067            },
18068        )),
18069        ..lsp::CompletionItem::default()
18070    };
18071    let items = iter::once(item_0.clone())
18072        .chain((11..51).map(|i| lsp::CompletionItem {
18073            label: format!("item_{}", i),
18074            insert_text: Some(format!("item_{}", i)),
18075            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
18076            ..lsp::CompletionItem::default()
18077        }))
18078        .collect::<Vec<_>>();
18079
18080    let default_commit_characters = vec!["?".to_string()];
18081    let default_data = json!({ "default": "data"});
18082    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
18083    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
18084    let default_edit_range = lsp::Range {
18085        start: lsp::Position {
18086            line: 0,
18087            character: 5,
18088        },
18089        end: lsp::Position {
18090            line: 0,
18091            character: 5,
18092        },
18093    };
18094
18095    let mut cx = EditorLspTestContext::new_rust(
18096        lsp::ServerCapabilities {
18097            completion_provider: Some(lsp::CompletionOptions {
18098                trigger_characters: Some(vec![".".to_string()]),
18099                resolve_provider: Some(true),
18100                ..Default::default()
18101            }),
18102            ..Default::default()
18103        },
18104        cx,
18105    )
18106    .await;
18107
18108    cx.set_state("fn main() { let a = 2ˇ; }");
18109    cx.simulate_keystroke(".");
18110
18111    let completion_data = default_data.clone();
18112    let completion_characters = default_commit_characters.clone();
18113    let completion_items = items.clone();
18114    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18115        let default_data = completion_data.clone();
18116        let default_commit_characters = completion_characters.clone();
18117        let items = completion_items.clone();
18118        async move {
18119            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
18120                items,
18121                item_defaults: Some(lsp::CompletionListItemDefaults {
18122                    data: Some(default_data.clone()),
18123                    commit_characters: Some(default_commit_characters.clone()),
18124                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
18125                        default_edit_range,
18126                    )),
18127                    insert_text_format: Some(default_insert_text_format),
18128                    insert_text_mode: Some(default_insert_text_mode),
18129                }),
18130                ..lsp::CompletionList::default()
18131            })))
18132        }
18133    })
18134    .next()
18135    .await;
18136
18137    let resolved_items = Arc::new(Mutex::new(Vec::new()));
18138    cx.lsp
18139        .server
18140        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
18141            let closure_resolved_items = resolved_items.clone();
18142            move |item_to_resolve, _| {
18143                let closure_resolved_items = closure_resolved_items.clone();
18144                async move {
18145                    closure_resolved_items.lock().push(item_to_resolve.clone());
18146                    Ok(item_to_resolve)
18147                }
18148            }
18149        })
18150        .detach();
18151
18152    cx.condition(|editor, _| editor.context_menu_visible())
18153        .await;
18154    cx.run_until_parked();
18155    cx.update_editor(|editor, _, _| {
18156        let menu = editor.context_menu.borrow_mut();
18157        match menu.as_ref().expect("should have the completions menu") {
18158            CodeContextMenu::Completions(completions_menu) => {
18159                assert_eq!(
18160                    completions_menu
18161                        .entries
18162                        .borrow()
18163                        .iter()
18164                        .map(|mat| mat.string.clone())
18165                        .collect::<Vec<String>>(),
18166                    items
18167                        .iter()
18168                        .map(|completion| completion.label.clone())
18169                        .collect::<Vec<String>>()
18170                );
18171            }
18172            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
18173        }
18174    });
18175    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
18176    // with 4 from the end.
18177    assert_eq!(
18178        *resolved_items.lock(),
18179        [&items[0..16], &items[items.len() - 4..items.len()]]
18180            .concat()
18181            .iter()
18182            .cloned()
18183            .map(|mut item| {
18184                if item.data.is_none() {
18185                    item.data = Some(default_data.clone());
18186                }
18187                item
18188            })
18189            .collect::<Vec<lsp::CompletionItem>>(),
18190        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
18191    );
18192    resolved_items.lock().clear();
18193
18194    cx.update_editor(|editor, window, cx| {
18195        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
18196    });
18197    cx.run_until_parked();
18198    // Completions that have already been resolved are skipped.
18199    assert_eq!(
18200        *resolved_items.lock(),
18201        items[items.len() - 17..items.len() - 4]
18202            .iter()
18203            .cloned()
18204            .map(|mut item| {
18205                if item.data.is_none() {
18206                    item.data = Some(default_data.clone());
18207                }
18208                item
18209            })
18210            .collect::<Vec<lsp::CompletionItem>>()
18211    );
18212    resolved_items.lock().clear();
18213}
18214
18215#[gpui::test]
18216async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
18217    init_test(cx, |_| {});
18218
18219    let mut cx = EditorLspTestContext::new(
18220        Language::new(
18221            LanguageConfig {
18222                matcher: LanguageMatcher {
18223                    path_suffixes: vec!["jsx".into()],
18224                    ..Default::default()
18225                },
18226                overrides: [(
18227                    "element".into(),
18228                    LanguageConfigOverride {
18229                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
18230                        ..Default::default()
18231                    },
18232                )]
18233                .into_iter()
18234                .collect(),
18235                ..Default::default()
18236            },
18237            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
18238        )
18239        .with_override_query("(jsx_self_closing_element) @element")
18240        .unwrap(),
18241        lsp::ServerCapabilities {
18242            completion_provider: Some(lsp::CompletionOptions {
18243                trigger_characters: Some(vec![":".to_string()]),
18244                ..Default::default()
18245            }),
18246            ..Default::default()
18247        },
18248        cx,
18249    )
18250    .await;
18251
18252    cx.lsp
18253        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
18254            Ok(Some(lsp::CompletionResponse::Array(vec![
18255                lsp::CompletionItem {
18256                    label: "bg-blue".into(),
18257                    ..Default::default()
18258                },
18259                lsp::CompletionItem {
18260                    label: "bg-red".into(),
18261                    ..Default::default()
18262                },
18263                lsp::CompletionItem {
18264                    label: "bg-yellow".into(),
18265                    ..Default::default()
18266                },
18267            ])))
18268        });
18269
18270    cx.set_state(r#"<p class="bgˇ" />"#);
18271
18272    // Trigger completion when typing a dash, because the dash is an extra
18273    // word character in the 'element' scope, which contains the cursor.
18274    cx.simulate_keystroke("-");
18275    cx.executor().run_until_parked();
18276    cx.update_editor(|editor, _, _| {
18277        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18278        {
18279            assert_eq!(
18280                completion_menu_entries(menu),
18281                &["bg-blue", "bg-red", "bg-yellow"]
18282            );
18283        } else {
18284            panic!("expected completion menu to be open");
18285        }
18286    });
18287
18288    cx.simulate_keystroke("l");
18289    cx.executor().run_until_parked();
18290    cx.update_editor(|editor, _, _| {
18291        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18292        {
18293            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
18294        } else {
18295            panic!("expected completion menu to be open");
18296        }
18297    });
18298
18299    // When filtering completions, consider the character after the '-' to
18300    // be the start of a subword.
18301    cx.set_state(r#"<p class="yelˇ" />"#);
18302    cx.simulate_keystroke("l");
18303    cx.executor().run_until_parked();
18304    cx.update_editor(|editor, _, _| {
18305        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18306        {
18307            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
18308        } else {
18309            panic!("expected completion menu to be open");
18310        }
18311    });
18312}
18313
18314fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
18315    let entries = menu.entries.borrow();
18316    entries.iter().map(|mat| mat.string.clone()).collect()
18317}
18318
18319#[gpui::test]
18320async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
18321    init_test(cx, |settings| {
18322        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
18323    });
18324
18325    let fs = FakeFs::new(cx.executor());
18326    fs.insert_file(path!("/file.ts"), Default::default()).await;
18327
18328    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
18329    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18330
18331    language_registry.add(Arc::new(Language::new(
18332        LanguageConfig {
18333            name: "TypeScript".into(),
18334            matcher: LanguageMatcher {
18335                path_suffixes: vec!["ts".to_string()],
18336                ..Default::default()
18337            },
18338            ..Default::default()
18339        },
18340        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
18341    )));
18342    update_test_language_settings(cx, |settings| {
18343        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
18344    });
18345
18346    let test_plugin = "test_plugin";
18347    let _ = language_registry.register_fake_lsp(
18348        "TypeScript",
18349        FakeLspAdapter {
18350            prettier_plugins: vec![test_plugin],
18351            ..Default::default()
18352        },
18353    );
18354
18355    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
18356    let buffer = project
18357        .update(cx, |project, cx| {
18358            project.open_local_buffer(path!("/file.ts"), cx)
18359        })
18360        .await
18361        .unwrap();
18362
18363    let buffer_text = "one\ntwo\nthree\n";
18364    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
18365    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
18366    editor.update_in(cx, |editor, window, cx| {
18367        editor.set_text(buffer_text, window, cx)
18368    });
18369
18370    editor
18371        .update_in(cx, |editor, window, cx| {
18372            editor.perform_format(
18373                project.clone(),
18374                FormatTrigger::Manual,
18375                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18376                window,
18377                cx,
18378            )
18379        })
18380        .unwrap()
18381        .await;
18382    assert_eq!(
18383        editor.update(cx, |editor, cx| editor.text(cx)),
18384        buffer_text.to_string() + prettier_format_suffix,
18385        "Test prettier formatting was not applied to the original buffer text",
18386    );
18387
18388    update_test_language_settings(cx, |settings| {
18389        settings.defaults.formatter = Some(FormatterList::default())
18390    });
18391    let format = editor.update_in(cx, |editor, window, cx| {
18392        editor.perform_format(
18393            project.clone(),
18394            FormatTrigger::Manual,
18395            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18396            window,
18397            cx,
18398        )
18399    });
18400    format.await.unwrap();
18401    assert_eq!(
18402        editor.update(cx, |editor, cx| editor.text(cx)),
18403        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
18404        "Autoformatting (via test prettier) was not applied to the original buffer text",
18405    );
18406}
18407
18408#[gpui::test]
18409async fn test_addition_reverts(cx: &mut TestAppContext) {
18410    init_test(cx, |_| {});
18411    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18412    let base_text = indoc! {r#"
18413        struct Row;
18414        struct Row1;
18415        struct Row2;
18416
18417        struct Row4;
18418        struct Row5;
18419        struct Row6;
18420
18421        struct Row8;
18422        struct Row9;
18423        struct Row10;"#};
18424
18425    // When addition hunks are not adjacent to carets, no hunk revert is performed
18426    assert_hunk_revert(
18427        indoc! {r#"struct Row;
18428                   struct Row1;
18429                   struct Row1.1;
18430                   struct Row1.2;
18431                   struct Row2;ˇ
18432
18433                   struct Row4;
18434                   struct Row5;
18435                   struct Row6;
18436
18437                   struct Row8;
18438                   ˇstruct Row9;
18439                   struct Row9.1;
18440                   struct Row9.2;
18441                   struct Row9.3;
18442                   struct Row10;"#},
18443        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18444        indoc! {r#"struct Row;
18445                   struct Row1;
18446                   struct Row1.1;
18447                   struct Row1.2;
18448                   struct Row2;ˇ
18449
18450                   struct Row4;
18451                   struct Row5;
18452                   struct Row6;
18453
18454                   struct Row8;
18455                   ˇstruct Row9;
18456                   struct Row9.1;
18457                   struct Row9.2;
18458                   struct Row9.3;
18459                   struct Row10;"#},
18460        base_text,
18461        &mut cx,
18462    );
18463    // Same for selections
18464    assert_hunk_revert(
18465        indoc! {r#"struct Row;
18466                   struct Row1;
18467                   struct Row2;
18468                   struct Row2.1;
18469                   struct Row2.2;
18470                   «ˇ
18471                   struct Row4;
18472                   struct» Row5;
18473                   «struct Row6;
18474                   ˇ»
18475                   struct Row9.1;
18476                   struct Row9.2;
18477                   struct Row9.3;
18478                   struct Row8;
18479                   struct Row9;
18480                   struct Row10;"#},
18481        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18482        indoc! {r#"struct Row;
18483                   struct Row1;
18484                   struct Row2;
18485                   struct Row2.1;
18486                   struct Row2.2;
18487                   «ˇ
18488                   struct Row4;
18489                   struct» Row5;
18490                   «struct Row6;
18491                   ˇ»
18492                   struct Row9.1;
18493                   struct Row9.2;
18494                   struct Row9.3;
18495                   struct Row8;
18496                   struct Row9;
18497                   struct Row10;"#},
18498        base_text,
18499        &mut cx,
18500    );
18501
18502    // When carets and selections intersect the addition hunks, those are reverted.
18503    // Adjacent carets got merged.
18504    assert_hunk_revert(
18505        indoc! {r#"struct Row;
18506                   ˇ// something on the top
18507                   struct Row1;
18508                   struct Row2;
18509                   struct Roˇw3.1;
18510                   struct Row2.2;
18511                   struct Row2.3;ˇ
18512
18513                   struct Row4;
18514                   struct ˇRow5.1;
18515                   struct Row5.2;
18516                   struct «Rowˇ»5.3;
18517                   struct Row5;
18518                   struct Row6;
18519                   ˇ
18520                   struct Row9.1;
18521                   struct «Rowˇ»9.2;
18522                   struct «ˇRow»9.3;
18523                   struct Row8;
18524                   struct Row9;
18525                   «ˇ// something on bottom»
18526                   struct Row10;"#},
18527        vec![
18528            DiffHunkStatusKind::Added,
18529            DiffHunkStatusKind::Added,
18530            DiffHunkStatusKind::Added,
18531            DiffHunkStatusKind::Added,
18532            DiffHunkStatusKind::Added,
18533        ],
18534        indoc! {r#"struct Row;
18535                   ˇstruct Row1;
18536                   struct Row2;
18537                   ˇ
18538                   struct Row4;
18539                   ˇstruct Row5;
18540                   struct Row6;
18541                   ˇ
18542                   ˇstruct Row8;
18543                   struct Row9;
18544                   ˇstruct Row10;"#},
18545        base_text,
18546        &mut cx,
18547    );
18548}
18549
18550#[gpui::test]
18551async fn test_modification_reverts(cx: &mut TestAppContext) {
18552    init_test(cx, |_| {});
18553    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18554    let base_text = indoc! {r#"
18555        struct Row;
18556        struct Row1;
18557        struct Row2;
18558
18559        struct Row4;
18560        struct Row5;
18561        struct Row6;
18562
18563        struct Row8;
18564        struct Row9;
18565        struct Row10;"#};
18566
18567    // Modification hunks behave the same as the addition ones.
18568    assert_hunk_revert(
18569        indoc! {r#"struct Row;
18570                   struct Row1;
18571                   struct Row33;
18572                   ˇ
18573                   struct Row4;
18574                   struct Row5;
18575                   struct Row6;
18576                   ˇ
18577                   struct Row99;
18578                   struct Row9;
18579                   struct Row10;"#},
18580        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18581        indoc! {r#"struct Row;
18582                   struct Row1;
18583                   struct Row33;
18584                   ˇ
18585                   struct Row4;
18586                   struct Row5;
18587                   struct Row6;
18588                   ˇ
18589                   struct Row99;
18590                   struct Row9;
18591                   struct Row10;"#},
18592        base_text,
18593        &mut cx,
18594    );
18595    assert_hunk_revert(
18596        indoc! {r#"struct Row;
18597                   struct Row1;
18598                   struct Row33;
18599                   «ˇ
18600                   struct Row4;
18601                   struct» Row5;
18602                   «struct Row6;
18603                   ˇ»
18604                   struct Row99;
18605                   struct Row9;
18606                   struct Row10;"#},
18607        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18608        indoc! {r#"struct Row;
18609                   struct Row1;
18610                   struct Row33;
18611                   «ˇ
18612                   struct Row4;
18613                   struct» Row5;
18614                   «struct Row6;
18615                   ˇ»
18616                   struct Row99;
18617                   struct Row9;
18618                   struct Row10;"#},
18619        base_text,
18620        &mut cx,
18621    );
18622
18623    assert_hunk_revert(
18624        indoc! {r#"ˇstruct Row1.1;
18625                   struct Row1;
18626                   «ˇstr»uct Row22;
18627
18628                   struct ˇRow44;
18629                   struct Row5;
18630                   struct «Rˇ»ow66;ˇ
18631
18632                   «struˇ»ct Row88;
18633                   struct Row9;
18634                   struct Row1011;ˇ"#},
18635        vec![
18636            DiffHunkStatusKind::Modified,
18637            DiffHunkStatusKind::Modified,
18638            DiffHunkStatusKind::Modified,
18639            DiffHunkStatusKind::Modified,
18640            DiffHunkStatusKind::Modified,
18641            DiffHunkStatusKind::Modified,
18642        ],
18643        indoc! {r#"struct Row;
18644                   ˇstruct Row1;
18645                   struct Row2;
18646                   ˇ
18647                   struct Row4;
18648                   ˇstruct Row5;
18649                   struct Row6;
18650                   ˇ
18651                   struct Row8;
18652                   ˇstruct Row9;
18653                   struct Row10;ˇ"#},
18654        base_text,
18655        &mut cx,
18656    );
18657}
18658
18659#[gpui::test]
18660async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
18661    init_test(cx, |_| {});
18662    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18663    let base_text = indoc! {r#"
18664        one
18665
18666        two
18667        three
18668        "#};
18669
18670    cx.set_head_text(base_text);
18671    cx.set_state("\nˇ\n");
18672    cx.executor().run_until_parked();
18673    cx.update_editor(|editor, _window, cx| {
18674        editor.expand_selected_diff_hunks(cx);
18675    });
18676    cx.executor().run_until_parked();
18677    cx.update_editor(|editor, window, cx| {
18678        editor.backspace(&Default::default(), window, cx);
18679    });
18680    cx.run_until_parked();
18681    cx.assert_state_with_diff(
18682        indoc! {r#"
18683
18684        - two
18685        - threeˇ
18686        +
18687        "#}
18688        .to_string(),
18689    );
18690}
18691
18692#[gpui::test]
18693async fn test_deletion_reverts(cx: &mut TestAppContext) {
18694    init_test(cx, |_| {});
18695    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18696    let base_text = indoc! {r#"struct Row;
18697struct Row1;
18698struct Row2;
18699
18700struct Row4;
18701struct Row5;
18702struct Row6;
18703
18704struct Row8;
18705struct Row9;
18706struct Row10;"#};
18707
18708    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
18709    assert_hunk_revert(
18710        indoc! {r#"struct Row;
18711                   struct Row2;
18712
18713                   ˇstruct Row4;
18714                   struct Row5;
18715                   struct Row6;
18716                   ˇ
18717                   struct Row8;
18718                   struct Row10;"#},
18719        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18720        indoc! {r#"struct Row;
18721                   struct Row2;
18722
18723                   ˇstruct Row4;
18724                   struct Row5;
18725                   struct Row6;
18726                   ˇ
18727                   struct Row8;
18728                   struct Row10;"#},
18729        base_text,
18730        &mut cx,
18731    );
18732    assert_hunk_revert(
18733        indoc! {r#"struct Row;
18734                   struct Row2;
18735
18736                   «ˇstruct Row4;
18737                   struct» Row5;
18738                   «struct Row6;
18739                   ˇ»
18740                   struct Row8;
18741                   struct Row10;"#},
18742        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18743        indoc! {r#"struct Row;
18744                   struct Row2;
18745
18746                   «ˇstruct Row4;
18747                   struct» Row5;
18748                   «struct Row6;
18749                   ˇ»
18750                   struct Row8;
18751                   struct Row10;"#},
18752        base_text,
18753        &mut cx,
18754    );
18755
18756    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
18757    assert_hunk_revert(
18758        indoc! {r#"struct Row;
18759                   ˇstruct Row2;
18760
18761                   struct Row4;
18762                   struct Row5;
18763                   struct Row6;
18764
18765                   struct Row8;ˇ
18766                   struct Row10;"#},
18767        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18768        indoc! {r#"struct Row;
18769                   struct Row1;
18770                   ˇstruct Row2;
18771
18772                   struct Row4;
18773                   struct Row5;
18774                   struct Row6;
18775
18776                   struct Row8;ˇ
18777                   struct Row9;
18778                   struct Row10;"#},
18779        base_text,
18780        &mut cx,
18781    );
18782    assert_hunk_revert(
18783        indoc! {r#"struct Row;
18784                   struct Row2«ˇ;
18785                   struct Row4;
18786                   struct» Row5;
18787                   «struct Row6;
18788
18789                   struct Row8;ˇ»
18790                   struct Row10;"#},
18791        vec![
18792            DiffHunkStatusKind::Deleted,
18793            DiffHunkStatusKind::Deleted,
18794            DiffHunkStatusKind::Deleted,
18795        ],
18796        indoc! {r#"struct Row;
18797                   struct Row1;
18798                   struct Row2«ˇ;
18799
18800                   struct Row4;
18801                   struct» Row5;
18802                   «struct Row6;
18803
18804                   struct Row8;ˇ»
18805                   struct Row9;
18806                   struct Row10;"#},
18807        base_text,
18808        &mut cx,
18809    );
18810}
18811
18812#[gpui::test]
18813async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
18814    init_test(cx, |_| {});
18815
18816    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
18817    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
18818    let base_text_3 =
18819        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
18820
18821    let text_1 = edit_first_char_of_every_line(base_text_1);
18822    let text_2 = edit_first_char_of_every_line(base_text_2);
18823    let text_3 = edit_first_char_of_every_line(base_text_3);
18824
18825    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
18826    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
18827    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
18828
18829    let multibuffer = cx.new(|cx| {
18830        let mut multibuffer = MultiBuffer::new(ReadWrite);
18831        multibuffer.push_excerpts(
18832            buffer_1.clone(),
18833            [
18834                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18835                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18836                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18837            ],
18838            cx,
18839        );
18840        multibuffer.push_excerpts(
18841            buffer_2.clone(),
18842            [
18843                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18844                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18845                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18846            ],
18847            cx,
18848        );
18849        multibuffer.push_excerpts(
18850            buffer_3.clone(),
18851            [
18852                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18853                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18854                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18855            ],
18856            cx,
18857        );
18858        multibuffer
18859    });
18860
18861    let fs = FakeFs::new(cx.executor());
18862    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
18863    let (editor, cx) = cx
18864        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
18865    editor.update_in(cx, |editor, _window, cx| {
18866        for (buffer, diff_base) in [
18867            (buffer_1.clone(), base_text_1),
18868            (buffer_2.clone(), base_text_2),
18869            (buffer_3.clone(), base_text_3),
18870        ] {
18871            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
18872            editor
18873                .buffer
18874                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
18875        }
18876    });
18877    cx.executor().run_until_parked();
18878
18879    editor.update_in(cx, |editor, window, cx| {
18880        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}");
18881        editor.select_all(&SelectAll, window, cx);
18882        editor.git_restore(&Default::default(), window, cx);
18883    });
18884    cx.executor().run_until_parked();
18885
18886    // When all ranges are selected, all buffer hunks are reverted.
18887    editor.update(cx, |editor, cx| {
18888        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");
18889    });
18890    buffer_1.update(cx, |buffer, _| {
18891        assert_eq!(buffer.text(), base_text_1);
18892    });
18893    buffer_2.update(cx, |buffer, _| {
18894        assert_eq!(buffer.text(), base_text_2);
18895    });
18896    buffer_3.update(cx, |buffer, _| {
18897        assert_eq!(buffer.text(), base_text_3);
18898    });
18899
18900    editor.update_in(cx, |editor, window, cx| {
18901        editor.undo(&Default::default(), window, cx);
18902    });
18903
18904    editor.update_in(cx, |editor, window, cx| {
18905        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18906            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
18907        });
18908        editor.git_restore(&Default::default(), window, cx);
18909    });
18910
18911    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
18912    // but not affect buffer_2 and its related excerpts.
18913    editor.update(cx, |editor, cx| {
18914        assert_eq!(
18915            editor.text(cx),
18916            "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}"
18917        );
18918    });
18919    buffer_1.update(cx, |buffer, _| {
18920        assert_eq!(buffer.text(), base_text_1);
18921    });
18922    buffer_2.update(cx, |buffer, _| {
18923        assert_eq!(
18924            buffer.text(),
18925            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
18926        );
18927    });
18928    buffer_3.update(cx, |buffer, _| {
18929        assert_eq!(
18930            buffer.text(),
18931            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
18932        );
18933    });
18934
18935    fn edit_first_char_of_every_line(text: &str) -> String {
18936        text.split('\n')
18937            .map(|line| format!("X{}", &line[1..]))
18938            .collect::<Vec<_>>()
18939            .join("\n")
18940    }
18941}
18942
18943#[gpui::test]
18944async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
18945    init_test(cx, |_| {});
18946
18947    let cols = 4;
18948    let rows = 10;
18949    let sample_text_1 = sample_text(rows, cols, 'a');
18950    assert_eq!(
18951        sample_text_1,
18952        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
18953    );
18954    let sample_text_2 = sample_text(rows, cols, 'l');
18955    assert_eq!(
18956        sample_text_2,
18957        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
18958    );
18959    let sample_text_3 = sample_text(rows, cols, 'v');
18960    assert_eq!(
18961        sample_text_3,
18962        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
18963    );
18964
18965    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
18966    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
18967    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
18968
18969    let multi_buffer = cx.new(|cx| {
18970        let mut multibuffer = MultiBuffer::new(ReadWrite);
18971        multibuffer.push_excerpts(
18972            buffer_1.clone(),
18973            [
18974                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18975                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18976                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18977            ],
18978            cx,
18979        );
18980        multibuffer.push_excerpts(
18981            buffer_2.clone(),
18982            [
18983                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18984                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18985                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18986            ],
18987            cx,
18988        );
18989        multibuffer.push_excerpts(
18990            buffer_3.clone(),
18991            [
18992                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18993                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18994                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18995            ],
18996            cx,
18997        );
18998        multibuffer
18999    });
19000
19001    let fs = FakeFs::new(cx.executor());
19002    fs.insert_tree(
19003        "/a",
19004        json!({
19005            "main.rs": sample_text_1,
19006            "other.rs": sample_text_2,
19007            "lib.rs": sample_text_3,
19008        }),
19009    )
19010    .await;
19011    let project = Project::test(fs, ["/a".as_ref()], cx).await;
19012    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19013    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19014    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19015        Editor::new(
19016            EditorMode::full(),
19017            multi_buffer,
19018            Some(project.clone()),
19019            window,
19020            cx,
19021        )
19022    });
19023    let multibuffer_item_id = workspace
19024        .update(cx, |workspace, window, cx| {
19025            assert!(
19026                workspace.active_item(cx).is_none(),
19027                "active item should be None before the first item is added"
19028            );
19029            workspace.add_item_to_active_pane(
19030                Box::new(multi_buffer_editor.clone()),
19031                None,
19032                true,
19033                window,
19034                cx,
19035            );
19036            let active_item = workspace
19037                .active_item(cx)
19038                .expect("should have an active item after adding the multi buffer");
19039            assert_eq!(
19040                active_item.buffer_kind(cx),
19041                ItemBufferKind::Multibuffer,
19042                "A multi buffer was expected to active after adding"
19043            );
19044            active_item.item_id()
19045        })
19046        .unwrap();
19047    cx.executor().run_until_parked();
19048
19049    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19050        editor.change_selections(
19051            SelectionEffects::scroll(Autoscroll::Next),
19052            window,
19053            cx,
19054            |s| s.select_ranges(Some(1..2)),
19055        );
19056        editor.open_excerpts(&OpenExcerpts, window, cx);
19057    });
19058    cx.executor().run_until_parked();
19059    let first_item_id = workspace
19060        .update(cx, |workspace, window, cx| {
19061            let active_item = workspace
19062                .active_item(cx)
19063                .expect("should have an active item after navigating into the 1st buffer");
19064            let first_item_id = active_item.item_id();
19065            assert_ne!(
19066                first_item_id, multibuffer_item_id,
19067                "Should navigate into the 1st buffer and activate it"
19068            );
19069            assert_eq!(
19070                active_item.buffer_kind(cx),
19071                ItemBufferKind::Singleton,
19072                "New active item should be a singleton buffer"
19073            );
19074            assert_eq!(
19075                active_item
19076                    .act_as::<Editor>(cx)
19077                    .expect("should have navigated into an editor for the 1st buffer")
19078                    .read(cx)
19079                    .text(cx),
19080                sample_text_1
19081            );
19082
19083            workspace
19084                .go_back(workspace.active_pane().downgrade(), window, cx)
19085                .detach_and_log_err(cx);
19086
19087            first_item_id
19088        })
19089        .unwrap();
19090    cx.executor().run_until_parked();
19091    workspace
19092        .update(cx, |workspace, _, cx| {
19093            let active_item = workspace
19094                .active_item(cx)
19095                .expect("should have an active item after navigating back");
19096            assert_eq!(
19097                active_item.item_id(),
19098                multibuffer_item_id,
19099                "Should navigate back to the multi buffer"
19100            );
19101            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19102        })
19103        .unwrap();
19104
19105    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19106        editor.change_selections(
19107            SelectionEffects::scroll(Autoscroll::Next),
19108            window,
19109            cx,
19110            |s| s.select_ranges(Some(39..40)),
19111        );
19112        editor.open_excerpts(&OpenExcerpts, window, cx);
19113    });
19114    cx.executor().run_until_parked();
19115    let second_item_id = workspace
19116        .update(cx, |workspace, window, cx| {
19117            let active_item = workspace
19118                .active_item(cx)
19119                .expect("should have an active item after navigating into the 2nd buffer");
19120            let second_item_id = active_item.item_id();
19121            assert_ne!(
19122                second_item_id, multibuffer_item_id,
19123                "Should navigate away from the multibuffer"
19124            );
19125            assert_ne!(
19126                second_item_id, first_item_id,
19127                "Should navigate into the 2nd buffer and activate it"
19128            );
19129            assert_eq!(
19130                active_item.buffer_kind(cx),
19131                ItemBufferKind::Singleton,
19132                "New active item should be a singleton buffer"
19133            );
19134            assert_eq!(
19135                active_item
19136                    .act_as::<Editor>(cx)
19137                    .expect("should have navigated into an editor")
19138                    .read(cx)
19139                    .text(cx),
19140                sample_text_2
19141            );
19142
19143            workspace
19144                .go_back(workspace.active_pane().downgrade(), window, cx)
19145                .detach_and_log_err(cx);
19146
19147            second_item_id
19148        })
19149        .unwrap();
19150    cx.executor().run_until_parked();
19151    workspace
19152        .update(cx, |workspace, _, cx| {
19153            let active_item = workspace
19154                .active_item(cx)
19155                .expect("should have an active item after navigating back from the 2nd buffer");
19156            assert_eq!(
19157                active_item.item_id(),
19158                multibuffer_item_id,
19159                "Should navigate back from the 2nd buffer to the multi buffer"
19160            );
19161            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19162        })
19163        .unwrap();
19164
19165    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19166        editor.change_selections(
19167            SelectionEffects::scroll(Autoscroll::Next),
19168            window,
19169            cx,
19170            |s| s.select_ranges(Some(70..70)),
19171        );
19172        editor.open_excerpts(&OpenExcerpts, window, cx);
19173    });
19174    cx.executor().run_until_parked();
19175    workspace
19176        .update(cx, |workspace, window, cx| {
19177            let active_item = workspace
19178                .active_item(cx)
19179                .expect("should have an active item after navigating into the 3rd buffer");
19180            let third_item_id = active_item.item_id();
19181            assert_ne!(
19182                third_item_id, multibuffer_item_id,
19183                "Should navigate into the 3rd buffer and activate it"
19184            );
19185            assert_ne!(third_item_id, first_item_id);
19186            assert_ne!(third_item_id, second_item_id);
19187            assert_eq!(
19188                active_item.buffer_kind(cx),
19189                ItemBufferKind::Singleton,
19190                "New active item should be a singleton buffer"
19191            );
19192            assert_eq!(
19193                active_item
19194                    .act_as::<Editor>(cx)
19195                    .expect("should have navigated into an editor")
19196                    .read(cx)
19197                    .text(cx),
19198                sample_text_3
19199            );
19200
19201            workspace
19202                .go_back(workspace.active_pane().downgrade(), window, cx)
19203                .detach_and_log_err(cx);
19204        })
19205        .unwrap();
19206    cx.executor().run_until_parked();
19207    workspace
19208        .update(cx, |workspace, _, cx| {
19209            let active_item = workspace
19210                .active_item(cx)
19211                .expect("should have an active item after navigating back from the 3rd buffer");
19212            assert_eq!(
19213                active_item.item_id(),
19214                multibuffer_item_id,
19215                "Should navigate back from the 3rd buffer to the multi buffer"
19216            );
19217            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19218        })
19219        .unwrap();
19220}
19221
19222#[gpui::test]
19223async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19224    init_test(cx, |_| {});
19225
19226    let mut cx = EditorTestContext::new(cx).await;
19227
19228    let diff_base = r#"
19229        use some::mod;
19230
19231        const A: u32 = 42;
19232
19233        fn main() {
19234            println!("hello");
19235
19236            println!("world");
19237        }
19238        "#
19239    .unindent();
19240
19241    cx.set_state(
19242        &r#"
19243        use some::modified;
19244
19245        ˇ
19246        fn main() {
19247            println!("hello there");
19248
19249            println!("around the");
19250            println!("world");
19251        }
19252        "#
19253        .unindent(),
19254    );
19255
19256    cx.set_head_text(&diff_base);
19257    executor.run_until_parked();
19258
19259    cx.update_editor(|editor, window, cx| {
19260        editor.go_to_next_hunk(&GoToHunk, window, cx);
19261        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19262    });
19263    executor.run_until_parked();
19264    cx.assert_state_with_diff(
19265        r#"
19266          use some::modified;
19267
19268
19269          fn main() {
19270        -     println!("hello");
19271        + ˇ    println!("hello there");
19272
19273              println!("around the");
19274              println!("world");
19275          }
19276        "#
19277        .unindent(),
19278    );
19279
19280    cx.update_editor(|editor, window, cx| {
19281        for _ in 0..2 {
19282            editor.go_to_next_hunk(&GoToHunk, window, cx);
19283            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19284        }
19285    });
19286    executor.run_until_parked();
19287    cx.assert_state_with_diff(
19288        r#"
19289        - use some::mod;
19290        + ˇuse some::modified;
19291
19292
19293          fn main() {
19294        -     println!("hello");
19295        +     println!("hello there");
19296
19297        +     println!("around the");
19298              println!("world");
19299          }
19300        "#
19301        .unindent(),
19302    );
19303
19304    cx.update_editor(|editor, window, cx| {
19305        editor.go_to_next_hunk(&GoToHunk, window, cx);
19306        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19307    });
19308    executor.run_until_parked();
19309    cx.assert_state_with_diff(
19310        r#"
19311        - use some::mod;
19312        + use some::modified;
19313
19314        - const A: u32 = 42;
19315          ˇ
19316          fn main() {
19317        -     println!("hello");
19318        +     println!("hello there");
19319
19320        +     println!("around the");
19321              println!("world");
19322          }
19323        "#
19324        .unindent(),
19325    );
19326
19327    cx.update_editor(|editor, window, cx| {
19328        editor.cancel(&Cancel, window, cx);
19329    });
19330
19331    cx.assert_state_with_diff(
19332        r#"
19333          use some::modified;
19334
19335          ˇ
19336          fn main() {
19337              println!("hello there");
19338
19339              println!("around the");
19340              println!("world");
19341          }
19342        "#
19343        .unindent(),
19344    );
19345}
19346
19347#[gpui::test]
19348async fn test_diff_base_change_with_expanded_diff_hunks(
19349    executor: BackgroundExecutor,
19350    cx: &mut TestAppContext,
19351) {
19352    init_test(cx, |_| {});
19353
19354    let mut cx = EditorTestContext::new(cx).await;
19355
19356    let diff_base = r#"
19357        use some::mod1;
19358        use some::mod2;
19359
19360        const A: u32 = 42;
19361        const B: u32 = 42;
19362        const C: u32 = 42;
19363
19364        fn main() {
19365            println!("hello");
19366
19367            println!("world");
19368        }
19369        "#
19370    .unindent();
19371
19372    cx.set_state(
19373        &r#"
19374        use some::mod2;
19375
19376        const A: u32 = 42;
19377        const C: u32 = 42;
19378
19379        fn main(ˇ) {
19380            //println!("hello");
19381
19382            println!("world");
19383            //
19384            //
19385        }
19386        "#
19387        .unindent(),
19388    );
19389
19390    cx.set_head_text(&diff_base);
19391    executor.run_until_parked();
19392
19393    cx.update_editor(|editor, window, cx| {
19394        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19395    });
19396    executor.run_until_parked();
19397    cx.assert_state_with_diff(
19398        r#"
19399        - use some::mod1;
19400          use some::mod2;
19401
19402          const A: u32 = 42;
19403        - const B: u32 = 42;
19404          const C: u32 = 42;
19405
19406          fn main(ˇ) {
19407        -     println!("hello");
19408        +     //println!("hello");
19409
19410              println!("world");
19411        +     //
19412        +     //
19413          }
19414        "#
19415        .unindent(),
19416    );
19417
19418    cx.set_head_text("new diff base!");
19419    executor.run_until_parked();
19420    cx.assert_state_with_diff(
19421        r#"
19422        - new diff base!
19423        + use some::mod2;
19424        +
19425        + const A: u32 = 42;
19426        + const C: u32 = 42;
19427        +
19428        + fn main(ˇ) {
19429        +     //println!("hello");
19430        +
19431        +     println!("world");
19432        +     //
19433        +     //
19434        + }
19435        "#
19436        .unindent(),
19437    );
19438}
19439
19440#[gpui::test]
19441async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
19442    init_test(cx, |_| {});
19443
19444    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19445    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19446    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19447    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19448    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
19449    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
19450
19451    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
19452    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
19453    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
19454
19455    let multi_buffer = cx.new(|cx| {
19456        let mut multibuffer = MultiBuffer::new(ReadWrite);
19457        multibuffer.push_excerpts(
19458            buffer_1.clone(),
19459            [
19460                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19461                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19462                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19463            ],
19464            cx,
19465        );
19466        multibuffer.push_excerpts(
19467            buffer_2.clone(),
19468            [
19469                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19470                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19471                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19472            ],
19473            cx,
19474        );
19475        multibuffer.push_excerpts(
19476            buffer_3.clone(),
19477            [
19478                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19479                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19480                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19481            ],
19482            cx,
19483        );
19484        multibuffer
19485    });
19486
19487    let editor =
19488        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19489    editor
19490        .update(cx, |editor, _window, cx| {
19491            for (buffer, diff_base) in [
19492                (buffer_1.clone(), file_1_old),
19493                (buffer_2.clone(), file_2_old),
19494                (buffer_3.clone(), file_3_old),
19495            ] {
19496                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
19497                editor
19498                    .buffer
19499                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
19500            }
19501        })
19502        .unwrap();
19503
19504    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19505    cx.run_until_parked();
19506
19507    cx.assert_editor_state(
19508        &"
19509            ˇaaa
19510            ccc
19511            ddd
19512
19513            ggg
19514            hhh
19515
19516
19517            lll
19518            mmm
19519            NNN
19520
19521            qqq
19522            rrr
19523
19524            uuu
19525            111
19526            222
19527            333
19528
19529            666
19530            777
19531
19532            000
19533            !!!"
19534        .unindent(),
19535    );
19536
19537    cx.update_editor(|editor, window, cx| {
19538        editor.select_all(&SelectAll, window, cx);
19539        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19540    });
19541    cx.executor().run_until_parked();
19542
19543    cx.assert_state_with_diff(
19544        "
19545            «aaa
19546          - bbb
19547            ccc
19548            ddd
19549
19550            ggg
19551            hhh
19552
19553
19554            lll
19555            mmm
19556          - nnn
19557          + NNN
19558
19559            qqq
19560            rrr
19561
19562            uuu
19563            111
19564            222
19565            333
19566
19567          + 666
19568            777
19569
19570            000
19571            !!!ˇ»"
19572            .unindent(),
19573    );
19574}
19575
19576#[gpui::test]
19577async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
19578    init_test(cx, |_| {});
19579
19580    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
19581    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
19582
19583    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
19584    let multi_buffer = cx.new(|cx| {
19585        let mut multibuffer = MultiBuffer::new(ReadWrite);
19586        multibuffer.push_excerpts(
19587            buffer.clone(),
19588            [
19589                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
19590                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
19591                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
19592            ],
19593            cx,
19594        );
19595        multibuffer
19596    });
19597
19598    let editor =
19599        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19600    editor
19601        .update(cx, |editor, _window, cx| {
19602            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
19603            editor
19604                .buffer
19605                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
19606        })
19607        .unwrap();
19608
19609    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19610    cx.run_until_parked();
19611
19612    cx.update_editor(|editor, window, cx| {
19613        editor.expand_all_diff_hunks(&Default::default(), window, cx)
19614    });
19615    cx.executor().run_until_parked();
19616
19617    // When the start of a hunk coincides with the start of its excerpt,
19618    // the hunk is expanded. When the start of a hunk is earlier than
19619    // the start of its excerpt, the hunk is not expanded.
19620    cx.assert_state_with_diff(
19621        "
19622            ˇaaa
19623          - bbb
19624          + BBB
19625
19626          - ddd
19627          - eee
19628          + DDD
19629          + EEE
19630            fff
19631
19632            iii
19633        "
19634        .unindent(),
19635    );
19636}
19637
19638#[gpui::test]
19639async fn test_edits_around_expanded_insertion_hunks(
19640    executor: BackgroundExecutor,
19641    cx: &mut TestAppContext,
19642) {
19643    init_test(cx, |_| {});
19644
19645    let mut cx = EditorTestContext::new(cx).await;
19646
19647    let diff_base = r#"
19648        use some::mod1;
19649        use some::mod2;
19650
19651        const A: u32 = 42;
19652
19653        fn main() {
19654            println!("hello");
19655
19656            println!("world");
19657        }
19658        "#
19659    .unindent();
19660    executor.run_until_parked();
19661    cx.set_state(
19662        &r#"
19663        use some::mod1;
19664        use some::mod2;
19665
19666        const A: u32 = 42;
19667        const B: u32 = 42;
19668        const C: u32 = 42;
19669        ˇ
19670
19671        fn main() {
19672            println!("hello");
19673
19674            println!("world");
19675        }
19676        "#
19677        .unindent(),
19678    );
19679
19680    cx.set_head_text(&diff_base);
19681    executor.run_until_parked();
19682
19683    cx.update_editor(|editor, window, cx| {
19684        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19685    });
19686    executor.run_until_parked();
19687
19688    cx.assert_state_with_diff(
19689        r#"
19690        use some::mod1;
19691        use some::mod2;
19692
19693        const A: u32 = 42;
19694      + const B: u32 = 42;
19695      + const C: u32 = 42;
19696      + ˇ
19697
19698        fn main() {
19699            println!("hello");
19700
19701            println!("world");
19702        }
19703      "#
19704        .unindent(),
19705    );
19706
19707    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
19708    executor.run_until_parked();
19709
19710    cx.assert_state_with_diff(
19711        r#"
19712        use some::mod1;
19713        use some::mod2;
19714
19715        const A: u32 = 42;
19716      + const B: u32 = 42;
19717      + const C: u32 = 42;
19718      + const D: u32 = 42;
19719      + ˇ
19720
19721        fn main() {
19722            println!("hello");
19723
19724            println!("world");
19725        }
19726      "#
19727        .unindent(),
19728    );
19729
19730    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
19731    executor.run_until_parked();
19732
19733    cx.assert_state_with_diff(
19734        r#"
19735        use some::mod1;
19736        use some::mod2;
19737
19738        const A: u32 = 42;
19739      + const B: u32 = 42;
19740      + const C: u32 = 42;
19741      + const D: u32 = 42;
19742      + const E: u32 = 42;
19743      + ˇ
19744
19745        fn main() {
19746            println!("hello");
19747
19748            println!("world");
19749        }
19750      "#
19751        .unindent(),
19752    );
19753
19754    cx.update_editor(|editor, window, cx| {
19755        editor.delete_line(&DeleteLine, window, cx);
19756    });
19757    executor.run_until_parked();
19758
19759    cx.assert_state_with_diff(
19760        r#"
19761        use some::mod1;
19762        use some::mod2;
19763
19764        const A: u32 = 42;
19765      + const B: u32 = 42;
19766      + const C: u32 = 42;
19767      + const D: u32 = 42;
19768      + const E: u32 = 42;
19769        ˇ
19770        fn main() {
19771            println!("hello");
19772
19773            println!("world");
19774        }
19775      "#
19776        .unindent(),
19777    );
19778
19779    cx.update_editor(|editor, window, cx| {
19780        editor.move_up(&MoveUp, window, cx);
19781        editor.delete_line(&DeleteLine, window, cx);
19782        editor.move_up(&MoveUp, window, cx);
19783        editor.delete_line(&DeleteLine, window, cx);
19784        editor.move_up(&MoveUp, window, cx);
19785        editor.delete_line(&DeleteLine, window, cx);
19786    });
19787    executor.run_until_parked();
19788    cx.assert_state_with_diff(
19789        r#"
19790        use some::mod1;
19791        use some::mod2;
19792
19793        const A: u32 = 42;
19794      + const B: u32 = 42;
19795        ˇ
19796        fn main() {
19797            println!("hello");
19798
19799            println!("world");
19800        }
19801      "#
19802        .unindent(),
19803    );
19804
19805    cx.update_editor(|editor, window, cx| {
19806        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
19807        editor.delete_line(&DeleteLine, window, cx);
19808    });
19809    executor.run_until_parked();
19810    cx.assert_state_with_diff(
19811        r#"
19812        ˇ
19813        fn main() {
19814            println!("hello");
19815
19816            println!("world");
19817        }
19818      "#
19819        .unindent(),
19820    );
19821}
19822
19823#[gpui::test]
19824async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
19825    init_test(cx, |_| {});
19826
19827    let mut cx = EditorTestContext::new(cx).await;
19828    cx.set_head_text(indoc! { "
19829        one
19830        two
19831        three
19832        four
19833        five
19834        "
19835    });
19836    cx.set_state(indoc! { "
19837        one
19838        ˇthree
19839        five
19840    "});
19841    cx.run_until_parked();
19842    cx.update_editor(|editor, window, cx| {
19843        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19844    });
19845    cx.assert_state_with_diff(
19846        indoc! { "
19847        one
19848      - two
19849        ˇthree
19850      - four
19851        five
19852    "}
19853        .to_string(),
19854    );
19855    cx.update_editor(|editor, window, cx| {
19856        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19857    });
19858
19859    cx.assert_state_with_diff(
19860        indoc! { "
19861        one
19862        ˇthree
19863        five
19864    "}
19865        .to_string(),
19866    );
19867
19868    cx.set_state(indoc! { "
19869        one
19870        ˇTWO
19871        three
19872        four
19873        five
19874    "});
19875    cx.run_until_parked();
19876    cx.update_editor(|editor, window, cx| {
19877        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19878    });
19879
19880    cx.assert_state_with_diff(
19881        indoc! { "
19882            one
19883          - two
19884          + ˇTWO
19885            three
19886            four
19887            five
19888        "}
19889        .to_string(),
19890    );
19891    cx.update_editor(|editor, window, cx| {
19892        editor.move_up(&Default::default(), window, cx);
19893        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19894    });
19895    cx.assert_state_with_diff(
19896        indoc! { "
19897            one
19898            ˇTWO
19899            three
19900            four
19901            five
19902        "}
19903        .to_string(),
19904    );
19905}
19906
19907#[gpui::test]
19908async fn test_edits_around_expanded_deletion_hunks(
19909    executor: BackgroundExecutor,
19910    cx: &mut TestAppContext,
19911) {
19912    init_test(cx, |_| {});
19913
19914    let mut cx = EditorTestContext::new(cx).await;
19915
19916    let diff_base = r#"
19917        use some::mod1;
19918        use some::mod2;
19919
19920        const A: u32 = 42;
19921        const B: u32 = 42;
19922        const C: u32 = 42;
19923
19924
19925        fn main() {
19926            println!("hello");
19927
19928            println!("world");
19929        }
19930    "#
19931    .unindent();
19932    executor.run_until_parked();
19933    cx.set_state(
19934        &r#"
19935        use some::mod1;
19936        use some::mod2;
19937
19938        ˇconst B: u32 = 42;
19939        const C: u32 = 42;
19940
19941
19942        fn main() {
19943            println!("hello");
19944
19945            println!("world");
19946        }
19947        "#
19948        .unindent(),
19949    );
19950
19951    cx.set_head_text(&diff_base);
19952    executor.run_until_parked();
19953
19954    cx.update_editor(|editor, window, cx| {
19955        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19956    });
19957    executor.run_until_parked();
19958
19959    cx.assert_state_with_diff(
19960        r#"
19961        use some::mod1;
19962        use some::mod2;
19963
19964      - const A: u32 = 42;
19965        ˇconst B: u32 = 42;
19966        const C: u32 = 42;
19967
19968
19969        fn main() {
19970            println!("hello");
19971
19972            println!("world");
19973        }
19974      "#
19975        .unindent(),
19976    );
19977
19978    cx.update_editor(|editor, window, cx| {
19979        editor.delete_line(&DeleteLine, window, cx);
19980    });
19981    executor.run_until_parked();
19982    cx.assert_state_with_diff(
19983        r#"
19984        use some::mod1;
19985        use some::mod2;
19986
19987      - const A: u32 = 42;
19988      - const B: u32 = 42;
19989        ˇconst C: u32 = 42;
19990
19991
19992        fn main() {
19993            println!("hello");
19994
19995            println!("world");
19996        }
19997      "#
19998        .unindent(),
19999    );
20000
20001    cx.update_editor(|editor, window, cx| {
20002        editor.delete_line(&DeleteLine, window, cx);
20003    });
20004    executor.run_until_parked();
20005    cx.assert_state_with_diff(
20006        r#"
20007        use some::mod1;
20008        use some::mod2;
20009
20010      - const A: u32 = 42;
20011      - const B: u32 = 42;
20012      - const C: u32 = 42;
20013        ˇ
20014
20015        fn main() {
20016            println!("hello");
20017
20018            println!("world");
20019        }
20020      "#
20021        .unindent(),
20022    );
20023
20024    cx.update_editor(|editor, window, cx| {
20025        editor.handle_input("replacement", window, cx);
20026    });
20027    executor.run_until_parked();
20028    cx.assert_state_with_diff(
20029        r#"
20030        use some::mod1;
20031        use some::mod2;
20032
20033      - const A: u32 = 42;
20034      - const B: u32 = 42;
20035      - const C: u32 = 42;
20036      -
20037      + replacementˇ
20038
20039        fn main() {
20040            println!("hello");
20041
20042            println!("world");
20043        }
20044      "#
20045        .unindent(),
20046    );
20047}
20048
20049#[gpui::test]
20050async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20051    init_test(cx, |_| {});
20052
20053    let mut cx = EditorTestContext::new(cx).await;
20054
20055    let base_text = r#"
20056        one
20057        two
20058        three
20059        four
20060        five
20061    "#
20062    .unindent();
20063    executor.run_until_parked();
20064    cx.set_state(
20065        &r#"
20066        one
20067        two
20068        fˇour
20069        five
20070        "#
20071        .unindent(),
20072    );
20073
20074    cx.set_head_text(&base_text);
20075    executor.run_until_parked();
20076
20077    cx.update_editor(|editor, window, cx| {
20078        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20079    });
20080    executor.run_until_parked();
20081
20082    cx.assert_state_with_diff(
20083        r#"
20084          one
20085          two
20086        - three
20087          fˇour
20088          five
20089        "#
20090        .unindent(),
20091    );
20092
20093    cx.update_editor(|editor, window, cx| {
20094        editor.backspace(&Backspace, window, cx);
20095        editor.backspace(&Backspace, window, cx);
20096    });
20097    executor.run_until_parked();
20098    cx.assert_state_with_diff(
20099        r#"
20100          one
20101          two
20102        - threeˇ
20103        - four
20104        + our
20105          five
20106        "#
20107        .unindent(),
20108    );
20109}
20110
20111#[gpui::test]
20112async fn test_edit_after_expanded_modification_hunk(
20113    executor: BackgroundExecutor,
20114    cx: &mut TestAppContext,
20115) {
20116    init_test(cx, |_| {});
20117
20118    let mut cx = EditorTestContext::new(cx).await;
20119
20120    let diff_base = r#"
20121        use some::mod1;
20122        use some::mod2;
20123
20124        const A: u32 = 42;
20125        const B: u32 = 42;
20126        const C: u32 = 42;
20127        const D: u32 = 42;
20128
20129
20130        fn main() {
20131            println!("hello");
20132
20133            println!("world");
20134        }"#
20135    .unindent();
20136
20137    cx.set_state(
20138        &r#"
20139        use some::mod1;
20140        use some::mod2;
20141
20142        const A: u32 = 42;
20143        const B: u32 = 42;
20144        const C: u32 = 43ˇ
20145        const D: u32 = 42;
20146
20147
20148        fn main() {
20149            println!("hello");
20150
20151            println!("world");
20152        }"#
20153        .unindent(),
20154    );
20155
20156    cx.set_head_text(&diff_base);
20157    executor.run_until_parked();
20158    cx.update_editor(|editor, window, cx| {
20159        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20160    });
20161    executor.run_until_parked();
20162
20163    cx.assert_state_with_diff(
20164        r#"
20165        use some::mod1;
20166        use some::mod2;
20167
20168        const A: u32 = 42;
20169        const B: u32 = 42;
20170      - const C: u32 = 42;
20171      + const C: u32 = 43ˇ
20172        const D: u32 = 42;
20173
20174
20175        fn main() {
20176            println!("hello");
20177
20178            println!("world");
20179        }"#
20180        .unindent(),
20181    );
20182
20183    cx.update_editor(|editor, window, cx| {
20184        editor.handle_input("\nnew_line\n", window, cx);
20185    });
20186    executor.run_until_parked();
20187
20188    cx.assert_state_with_diff(
20189        r#"
20190        use some::mod1;
20191        use some::mod2;
20192
20193        const A: u32 = 42;
20194        const B: u32 = 42;
20195      - const C: u32 = 42;
20196      + const C: u32 = 43
20197      + new_line
20198      + ˇ
20199        const D: u32 = 42;
20200
20201
20202        fn main() {
20203            println!("hello");
20204
20205            println!("world");
20206        }"#
20207        .unindent(),
20208    );
20209}
20210
20211#[gpui::test]
20212async fn test_stage_and_unstage_added_file_hunk(
20213    executor: BackgroundExecutor,
20214    cx: &mut TestAppContext,
20215) {
20216    init_test(cx, |_| {});
20217
20218    let mut cx = EditorTestContext::new(cx).await;
20219    cx.update_editor(|editor, _, cx| {
20220        editor.set_expand_all_diff_hunks(cx);
20221    });
20222
20223    let working_copy = r#"
20224            ˇfn main() {
20225                println!("hello, world!");
20226            }
20227        "#
20228    .unindent();
20229
20230    cx.set_state(&working_copy);
20231    executor.run_until_parked();
20232
20233    cx.assert_state_with_diff(
20234        r#"
20235            + ˇfn main() {
20236            +     println!("hello, world!");
20237            + }
20238        "#
20239        .unindent(),
20240    );
20241    cx.assert_index_text(None);
20242
20243    cx.update_editor(|editor, window, cx| {
20244        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20245    });
20246    executor.run_until_parked();
20247    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
20248    cx.assert_state_with_diff(
20249        r#"
20250            + ˇfn main() {
20251            +     println!("hello, world!");
20252            + }
20253        "#
20254        .unindent(),
20255    );
20256
20257    cx.update_editor(|editor, window, cx| {
20258        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20259    });
20260    executor.run_until_parked();
20261    cx.assert_index_text(None);
20262}
20263
20264async fn setup_indent_guides_editor(
20265    text: &str,
20266    cx: &mut TestAppContext,
20267) -> (BufferId, EditorTestContext) {
20268    init_test(cx, |_| {});
20269
20270    let mut cx = EditorTestContext::new(cx).await;
20271
20272    let buffer_id = cx.update_editor(|editor, window, cx| {
20273        editor.set_text(text, window, cx);
20274        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
20275
20276        buffer_ids[0]
20277    });
20278
20279    (buffer_id, cx)
20280}
20281
20282fn assert_indent_guides(
20283    range: Range<u32>,
20284    expected: Vec<IndentGuide>,
20285    active_indices: Option<Vec<usize>>,
20286    cx: &mut EditorTestContext,
20287) {
20288    let indent_guides = cx.update_editor(|editor, window, cx| {
20289        let snapshot = editor.snapshot(window, cx).display_snapshot;
20290        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
20291            editor,
20292            MultiBufferRow(range.start)..MultiBufferRow(range.end),
20293            true,
20294            &snapshot,
20295            cx,
20296        );
20297
20298        indent_guides.sort_by(|a, b| {
20299            a.depth.cmp(&b.depth).then(
20300                a.start_row
20301                    .cmp(&b.start_row)
20302                    .then(a.end_row.cmp(&b.end_row)),
20303            )
20304        });
20305        indent_guides
20306    });
20307
20308    if let Some(expected) = active_indices {
20309        let active_indices = cx.update_editor(|editor, window, cx| {
20310            let snapshot = editor.snapshot(window, cx).display_snapshot;
20311            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
20312        });
20313
20314        assert_eq!(
20315            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
20316            expected,
20317            "Active indent guide indices do not match"
20318        );
20319    }
20320
20321    assert_eq!(indent_guides, expected, "Indent guides do not match");
20322}
20323
20324fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
20325    IndentGuide {
20326        buffer_id,
20327        start_row: MultiBufferRow(start_row),
20328        end_row: MultiBufferRow(end_row),
20329        depth,
20330        tab_size: 4,
20331        settings: IndentGuideSettings {
20332            enabled: true,
20333            line_width: 1,
20334            active_line_width: 1,
20335            coloring: IndentGuideColoring::default(),
20336            background_coloring: IndentGuideBackgroundColoring::default(),
20337        },
20338    }
20339}
20340
20341#[gpui::test]
20342async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
20343    let (buffer_id, mut cx) = setup_indent_guides_editor(
20344        &"
20345        fn main() {
20346            let a = 1;
20347        }"
20348        .unindent(),
20349        cx,
20350    )
20351    .await;
20352
20353    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20354}
20355
20356#[gpui::test]
20357async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
20358    let (buffer_id, mut cx) = setup_indent_guides_editor(
20359        &"
20360        fn main() {
20361            let a = 1;
20362            let b = 2;
20363        }"
20364        .unindent(),
20365        cx,
20366    )
20367    .await;
20368
20369    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
20370}
20371
20372#[gpui::test]
20373async fn test_indent_guide_nested(cx: &mut TestAppContext) {
20374    let (buffer_id, mut cx) = setup_indent_guides_editor(
20375        &"
20376        fn main() {
20377            let a = 1;
20378            if a == 3 {
20379                let b = 2;
20380            } else {
20381                let c = 3;
20382            }
20383        }"
20384        .unindent(),
20385        cx,
20386    )
20387    .await;
20388
20389    assert_indent_guides(
20390        0..8,
20391        vec![
20392            indent_guide(buffer_id, 1, 6, 0),
20393            indent_guide(buffer_id, 3, 3, 1),
20394            indent_guide(buffer_id, 5, 5, 1),
20395        ],
20396        None,
20397        &mut cx,
20398    );
20399}
20400
20401#[gpui::test]
20402async fn test_indent_guide_tab(cx: &mut TestAppContext) {
20403    let (buffer_id, mut cx) = setup_indent_guides_editor(
20404        &"
20405        fn main() {
20406            let a = 1;
20407                let b = 2;
20408            let c = 3;
20409        }"
20410        .unindent(),
20411        cx,
20412    )
20413    .await;
20414
20415    assert_indent_guides(
20416        0..5,
20417        vec![
20418            indent_guide(buffer_id, 1, 3, 0),
20419            indent_guide(buffer_id, 2, 2, 1),
20420        ],
20421        None,
20422        &mut cx,
20423    );
20424}
20425
20426#[gpui::test]
20427async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
20428    let (buffer_id, mut cx) = setup_indent_guides_editor(
20429        &"
20430        fn main() {
20431            let a = 1;
20432
20433            let c = 3;
20434        }"
20435        .unindent(),
20436        cx,
20437    )
20438    .await;
20439
20440    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
20441}
20442
20443#[gpui::test]
20444async fn test_indent_guide_complex(cx: &mut TestAppContext) {
20445    let (buffer_id, mut cx) = setup_indent_guides_editor(
20446        &"
20447        fn main() {
20448            let a = 1;
20449
20450            let c = 3;
20451
20452            if a == 3 {
20453                let b = 2;
20454            } else {
20455                let c = 3;
20456            }
20457        }"
20458        .unindent(),
20459        cx,
20460    )
20461    .await;
20462
20463    assert_indent_guides(
20464        0..11,
20465        vec![
20466            indent_guide(buffer_id, 1, 9, 0),
20467            indent_guide(buffer_id, 6, 6, 1),
20468            indent_guide(buffer_id, 8, 8, 1),
20469        ],
20470        None,
20471        &mut cx,
20472    );
20473}
20474
20475#[gpui::test]
20476async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
20477    let (buffer_id, mut cx) = setup_indent_guides_editor(
20478        &"
20479        fn main() {
20480            let a = 1;
20481
20482            let c = 3;
20483
20484            if a == 3 {
20485                let b = 2;
20486            } else {
20487                let c = 3;
20488            }
20489        }"
20490        .unindent(),
20491        cx,
20492    )
20493    .await;
20494
20495    assert_indent_guides(
20496        1..11,
20497        vec![
20498            indent_guide(buffer_id, 1, 9, 0),
20499            indent_guide(buffer_id, 6, 6, 1),
20500            indent_guide(buffer_id, 8, 8, 1),
20501        ],
20502        None,
20503        &mut cx,
20504    );
20505}
20506
20507#[gpui::test]
20508async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
20509    let (buffer_id, mut cx) = setup_indent_guides_editor(
20510        &"
20511        fn main() {
20512            let a = 1;
20513
20514            let c = 3;
20515
20516            if a == 3 {
20517                let b = 2;
20518            } else {
20519                let c = 3;
20520            }
20521        }"
20522        .unindent(),
20523        cx,
20524    )
20525    .await;
20526
20527    assert_indent_guides(
20528        1..10,
20529        vec![
20530            indent_guide(buffer_id, 1, 9, 0),
20531            indent_guide(buffer_id, 6, 6, 1),
20532            indent_guide(buffer_id, 8, 8, 1),
20533        ],
20534        None,
20535        &mut cx,
20536    );
20537}
20538
20539#[gpui::test]
20540async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
20541    let (buffer_id, mut cx) = setup_indent_guides_editor(
20542        &"
20543        fn main() {
20544            if a {
20545                b(
20546                    c,
20547                    d,
20548                )
20549            } else {
20550                e(
20551                    f
20552                )
20553            }
20554        }"
20555        .unindent(),
20556        cx,
20557    )
20558    .await;
20559
20560    assert_indent_guides(
20561        0..11,
20562        vec![
20563            indent_guide(buffer_id, 1, 10, 0),
20564            indent_guide(buffer_id, 2, 5, 1),
20565            indent_guide(buffer_id, 7, 9, 1),
20566            indent_guide(buffer_id, 3, 4, 2),
20567            indent_guide(buffer_id, 8, 8, 2),
20568        ],
20569        None,
20570        &mut cx,
20571    );
20572
20573    cx.update_editor(|editor, window, cx| {
20574        editor.fold_at(MultiBufferRow(2), window, cx);
20575        assert_eq!(
20576            editor.display_text(cx),
20577            "
20578            fn main() {
20579                if a {
20580                    b(⋯
20581                    )
20582                } else {
20583                    e(
20584                        f
20585                    )
20586                }
20587            }"
20588            .unindent()
20589        );
20590    });
20591
20592    assert_indent_guides(
20593        0..11,
20594        vec![
20595            indent_guide(buffer_id, 1, 10, 0),
20596            indent_guide(buffer_id, 2, 5, 1),
20597            indent_guide(buffer_id, 7, 9, 1),
20598            indent_guide(buffer_id, 8, 8, 2),
20599        ],
20600        None,
20601        &mut cx,
20602    );
20603}
20604
20605#[gpui::test]
20606async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
20607    let (buffer_id, mut cx) = setup_indent_guides_editor(
20608        &"
20609        block1
20610            block2
20611                block3
20612                    block4
20613            block2
20614        block1
20615        block1"
20616            .unindent(),
20617        cx,
20618    )
20619    .await;
20620
20621    assert_indent_guides(
20622        1..10,
20623        vec![
20624            indent_guide(buffer_id, 1, 4, 0),
20625            indent_guide(buffer_id, 2, 3, 1),
20626            indent_guide(buffer_id, 3, 3, 2),
20627        ],
20628        None,
20629        &mut cx,
20630    );
20631}
20632
20633#[gpui::test]
20634async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
20635    let (buffer_id, mut cx) = setup_indent_guides_editor(
20636        &"
20637        block1
20638            block2
20639                block3
20640
20641        block1
20642        block1"
20643            .unindent(),
20644        cx,
20645    )
20646    .await;
20647
20648    assert_indent_guides(
20649        0..6,
20650        vec![
20651            indent_guide(buffer_id, 1, 2, 0),
20652            indent_guide(buffer_id, 2, 2, 1),
20653        ],
20654        None,
20655        &mut cx,
20656    );
20657}
20658
20659#[gpui::test]
20660async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
20661    let (buffer_id, mut cx) = setup_indent_guides_editor(
20662        &"
20663        function component() {
20664        \treturn (
20665        \t\t\t
20666        \t\t<div>
20667        \t\t\t<abc></abc>
20668        \t\t</div>
20669        \t)
20670        }"
20671        .unindent(),
20672        cx,
20673    )
20674    .await;
20675
20676    assert_indent_guides(
20677        0..8,
20678        vec![
20679            indent_guide(buffer_id, 1, 6, 0),
20680            indent_guide(buffer_id, 2, 5, 1),
20681            indent_guide(buffer_id, 4, 4, 2),
20682        ],
20683        None,
20684        &mut cx,
20685    );
20686}
20687
20688#[gpui::test]
20689async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
20690    let (buffer_id, mut cx) = setup_indent_guides_editor(
20691        &"
20692        function component() {
20693        \treturn (
20694        \t
20695        \t\t<div>
20696        \t\t\t<abc></abc>
20697        \t\t</div>
20698        \t)
20699        }"
20700        .unindent(),
20701        cx,
20702    )
20703    .await;
20704
20705    assert_indent_guides(
20706        0..8,
20707        vec![
20708            indent_guide(buffer_id, 1, 6, 0),
20709            indent_guide(buffer_id, 2, 5, 1),
20710            indent_guide(buffer_id, 4, 4, 2),
20711        ],
20712        None,
20713        &mut cx,
20714    );
20715}
20716
20717#[gpui::test]
20718async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
20719    let (buffer_id, mut cx) = setup_indent_guides_editor(
20720        &"
20721        block1
20722
20723
20724
20725            block2
20726        "
20727        .unindent(),
20728        cx,
20729    )
20730    .await;
20731
20732    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20733}
20734
20735#[gpui::test]
20736async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
20737    let (buffer_id, mut cx) = setup_indent_guides_editor(
20738        &"
20739        def a:
20740        \tb = 3
20741        \tif True:
20742        \t\tc = 4
20743        \t\td = 5
20744        \tprint(b)
20745        "
20746        .unindent(),
20747        cx,
20748    )
20749    .await;
20750
20751    assert_indent_guides(
20752        0..6,
20753        vec![
20754            indent_guide(buffer_id, 1, 5, 0),
20755            indent_guide(buffer_id, 3, 4, 1),
20756        ],
20757        None,
20758        &mut cx,
20759    );
20760}
20761
20762#[gpui::test]
20763async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
20764    let (buffer_id, mut cx) = setup_indent_guides_editor(
20765        &"
20766    fn main() {
20767        let a = 1;
20768    }"
20769        .unindent(),
20770        cx,
20771    )
20772    .await;
20773
20774    cx.update_editor(|editor, window, cx| {
20775        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20776            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20777        });
20778    });
20779
20780    assert_indent_guides(
20781        0..3,
20782        vec![indent_guide(buffer_id, 1, 1, 0)],
20783        Some(vec![0]),
20784        &mut cx,
20785    );
20786}
20787
20788#[gpui::test]
20789async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
20790    let (buffer_id, mut cx) = setup_indent_guides_editor(
20791        &"
20792    fn main() {
20793        if 1 == 2 {
20794            let a = 1;
20795        }
20796    }"
20797        .unindent(),
20798        cx,
20799    )
20800    .await;
20801
20802    cx.update_editor(|editor, window, cx| {
20803        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20804            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20805        });
20806    });
20807
20808    assert_indent_guides(
20809        0..4,
20810        vec![
20811            indent_guide(buffer_id, 1, 3, 0),
20812            indent_guide(buffer_id, 2, 2, 1),
20813        ],
20814        Some(vec![1]),
20815        &mut cx,
20816    );
20817
20818    cx.update_editor(|editor, window, cx| {
20819        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20820            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20821        });
20822    });
20823
20824    assert_indent_guides(
20825        0..4,
20826        vec![
20827            indent_guide(buffer_id, 1, 3, 0),
20828            indent_guide(buffer_id, 2, 2, 1),
20829        ],
20830        Some(vec![1]),
20831        &mut cx,
20832    );
20833
20834    cx.update_editor(|editor, window, cx| {
20835        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20836            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
20837        });
20838    });
20839
20840    assert_indent_guides(
20841        0..4,
20842        vec![
20843            indent_guide(buffer_id, 1, 3, 0),
20844            indent_guide(buffer_id, 2, 2, 1),
20845        ],
20846        Some(vec![0]),
20847        &mut cx,
20848    );
20849}
20850
20851#[gpui::test]
20852async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
20853    let (buffer_id, mut cx) = setup_indent_guides_editor(
20854        &"
20855    fn main() {
20856        let a = 1;
20857
20858        let b = 2;
20859    }"
20860        .unindent(),
20861        cx,
20862    )
20863    .await;
20864
20865    cx.update_editor(|editor, window, cx| {
20866        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20867            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20868        });
20869    });
20870
20871    assert_indent_guides(
20872        0..5,
20873        vec![indent_guide(buffer_id, 1, 3, 0)],
20874        Some(vec![0]),
20875        &mut cx,
20876    );
20877}
20878
20879#[gpui::test]
20880async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
20881    let (buffer_id, mut cx) = setup_indent_guides_editor(
20882        &"
20883    def m:
20884        a = 1
20885        pass"
20886            .unindent(),
20887        cx,
20888    )
20889    .await;
20890
20891    cx.update_editor(|editor, window, cx| {
20892        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20893            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20894        });
20895    });
20896
20897    assert_indent_guides(
20898        0..3,
20899        vec![indent_guide(buffer_id, 1, 2, 0)],
20900        Some(vec![0]),
20901        &mut cx,
20902    );
20903}
20904
20905#[gpui::test]
20906async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
20907    init_test(cx, |_| {});
20908    let mut cx = EditorTestContext::new(cx).await;
20909    let text = indoc! {
20910        "
20911        impl A {
20912            fn b() {
20913                0;
20914                3;
20915                5;
20916                6;
20917                7;
20918            }
20919        }
20920        "
20921    };
20922    let base_text = indoc! {
20923        "
20924        impl A {
20925            fn b() {
20926                0;
20927                1;
20928                2;
20929                3;
20930                4;
20931            }
20932            fn c() {
20933                5;
20934                6;
20935                7;
20936            }
20937        }
20938        "
20939    };
20940
20941    cx.update_editor(|editor, window, cx| {
20942        editor.set_text(text, window, cx);
20943
20944        editor.buffer().update(cx, |multibuffer, cx| {
20945            let buffer = multibuffer.as_singleton().unwrap();
20946            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
20947
20948            multibuffer.set_all_diff_hunks_expanded(cx);
20949            multibuffer.add_diff(diff, cx);
20950
20951            buffer.read(cx).remote_id()
20952        })
20953    });
20954    cx.run_until_parked();
20955
20956    cx.assert_state_with_diff(
20957        indoc! { "
20958          impl A {
20959              fn b() {
20960                  0;
20961        -         1;
20962        -         2;
20963                  3;
20964        -         4;
20965        -     }
20966        -     fn c() {
20967                  5;
20968                  6;
20969                  7;
20970              }
20971          }
20972          ˇ"
20973        }
20974        .to_string(),
20975    );
20976
20977    let mut actual_guides = cx.update_editor(|editor, window, cx| {
20978        editor
20979            .snapshot(window, cx)
20980            .buffer_snapshot()
20981            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
20982            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
20983            .collect::<Vec<_>>()
20984    });
20985    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
20986    assert_eq!(
20987        actual_guides,
20988        vec![
20989            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
20990            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
20991            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
20992        ]
20993    );
20994}
20995
20996#[gpui::test]
20997async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20998    init_test(cx, |_| {});
20999    let mut cx = EditorTestContext::new(cx).await;
21000
21001    let diff_base = r#"
21002        a
21003        b
21004        c
21005        "#
21006    .unindent();
21007
21008    cx.set_state(
21009        &r#"
21010        ˇA
21011        b
21012        C
21013        "#
21014        .unindent(),
21015    );
21016    cx.set_head_text(&diff_base);
21017    cx.update_editor(|editor, window, cx| {
21018        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21019    });
21020    executor.run_until_parked();
21021
21022    let both_hunks_expanded = r#"
21023        - a
21024        + ˇA
21025          b
21026        - c
21027        + C
21028        "#
21029    .unindent();
21030
21031    cx.assert_state_with_diff(both_hunks_expanded.clone());
21032
21033    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21034        let snapshot = editor.snapshot(window, cx);
21035        let hunks = editor
21036            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21037            .collect::<Vec<_>>();
21038        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21039        let buffer_id = hunks[0].buffer_id;
21040        hunks
21041            .into_iter()
21042            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
21043            .collect::<Vec<_>>()
21044    });
21045    assert_eq!(hunk_ranges.len(), 2);
21046
21047    cx.update_editor(|editor, _, cx| {
21048        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21049    });
21050    executor.run_until_parked();
21051
21052    let second_hunk_expanded = r#"
21053          ˇA
21054          b
21055        - c
21056        + C
21057        "#
21058    .unindent();
21059
21060    cx.assert_state_with_diff(second_hunk_expanded);
21061
21062    cx.update_editor(|editor, _, cx| {
21063        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21064    });
21065    executor.run_until_parked();
21066
21067    cx.assert_state_with_diff(both_hunks_expanded.clone());
21068
21069    cx.update_editor(|editor, _, cx| {
21070        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21071    });
21072    executor.run_until_parked();
21073
21074    let first_hunk_expanded = r#"
21075        - a
21076        + ˇA
21077          b
21078          C
21079        "#
21080    .unindent();
21081
21082    cx.assert_state_with_diff(first_hunk_expanded);
21083
21084    cx.update_editor(|editor, _, cx| {
21085        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21086    });
21087    executor.run_until_parked();
21088
21089    cx.assert_state_with_diff(both_hunks_expanded);
21090
21091    cx.set_state(
21092        &r#"
21093        ˇA
21094        b
21095        "#
21096        .unindent(),
21097    );
21098    cx.run_until_parked();
21099
21100    // TODO this cursor position seems bad
21101    cx.assert_state_with_diff(
21102        r#"
21103        - ˇa
21104        + A
21105          b
21106        "#
21107        .unindent(),
21108    );
21109
21110    cx.update_editor(|editor, window, cx| {
21111        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21112    });
21113
21114    cx.assert_state_with_diff(
21115        r#"
21116            - ˇa
21117            + A
21118              b
21119            - c
21120            "#
21121        .unindent(),
21122    );
21123
21124    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21125        let snapshot = editor.snapshot(window, cx);
21126        let hunks = editor
21127            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21128            .collect::<Vec<_>>();
21129        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21130        let buffer_id = hunks[0].buffer_id;
21131        hunks
21132            .into_iter()
21133            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
21134            .collect::<Vec<_>>()
21135    });
21136    assert_eq!(hunk_ranges.len(), 2);
21137
21138    cx.update_editor(|editor, _, cx| {
21139        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21140    });
21141    executor.run_until_parked();
21142
21143    cx.assert_state_with_diff(
21144        r#"
21145        - ˇa
21146        + A
21147          b
21148        "#
21149        .unindent(),
21150    );
21151}
21152
21153#[gpui::test]
21154async fn test_toggle_deletion_hunk_at_start_of_file(
21155    executor: BackgroundExecutor,
21156    cx: &mut TestAppContext,
21157) {
21158    init_test(cx, |_| {});
21159    let mut cx = EditorTestContext::new(cx).await;
21160
21161    let diff_base = r#"
21162        a
21163        b
21164        c
21165        "#
21166    .unindent();
21167
21168    cx.set_state(
21169        &r#"
21170        ˇb
21171        c
21172        "#
21173        .unindent(),
21174    );
21175    cx.set_head_text(&diff_base);
21176    cx.update_editor(|editor, window, cx| {
21177        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21178    });
21179    executor.run_until_parked();
21180
21181    let hunk_expanded = r#"
21182        - a
21183          ˇb
21184          c
21185        "#
21186    .unindent();
21187
21188    cx.assert_state_with_diff(hunk_expanded.clone());
21189
21190    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21191        let snapshot = editor.snapshot(window, cx);
21192        let hunks = editor
21193            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21194            .collect::<Vec<_>>();
21195        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21196        let buffer_id = hunks[0].buffer_id;
21197        hunks
21198            .into_iter()
21199            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
21200            .collect::<Vec<_>>()
21201    });
21202    assert_eq!(hunk_ranges.len(), 1);
21203
21204    cx.update_editor(|editor, _, cx| {
21205        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21206    });
21207    executor.run_until_parked();
21208
21209    let hunk_collapsed = r#"
21210          ˇb
21211          c
21212        "#
21213    .unindent();
21214
21215    cx.assert_state_with_diff(hunk_collapsed);
21216
21217    cx.update_editor(|editor, _, cx| {
21218        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21219    });
21220    executor.run_until_parked();
21221
21222    cx.assert_state_with_diff(hunk_expanded);
21223}
21224
21225#[gpui::test]
21226async fn test_display_diff_hunks(cx: &mut TestAppContext) {
21227    init_test(cx, |_| {});
21228
21229    let fs = FakeFs::new(cx.executor());
21230    fs.insert_tree(
21231        path!("/test"),
21232        json!({
21233            ".git": {},
21234            "file-1": "ONE\n",
21235            "file-2": "TWO\n",
21236            "file-3": "THREE\n",
21237        }),
21238    )
21239    .await;
21240
21241    fs.set_head_for_repo(
21242        path!("/test/.git").as_ref(),
21243        &[
21244            ("file-1", "one\n".into()),
21245            ("file-2", "two\n".into()),
21246            ("file-3", "three\n".into()),
21247        ],
21248        "deadbeef",
21249    );
21250
21251    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
21252    let mut buffers = vec![];
21253    for i in 1..=3 {
21254        let buffer = project
21255            .update(cx, |project, cx| {
21256                let path = format!(path!("/test/file-{}"), i);
21257                project.open_local_buffer(path, cx)
21258            })
21259            .await
21260            .unwrap();
21261        buffers.push(buffer);
21262    }
21263
21264    let multibuffer = cx.new(|cx| {
21265        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
21266        multibuffer.set_all_diff_hunks_expanded(cx);
21267        for buffer in &buffers {
21268            let snapshot = buffer.read(cx).snapshot();
21269            multibuffer.set_excerpts_for_path(
21270                PathKey::with_sort_prefix(0, buffer.read(cx).file().unwrap().path().clone()),
21271                buffer.clone(),
21272                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
21273                2,
21274                cx,
21275            );
21276        }
21277        multibuffer
21278    });
21279
21280    let editor = cx.add_window(|window, cx| {
21281        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
21282    });
21283    cx.run_until_parked();
21284
21285    let snapshot = editor
21286        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21287        .unwrap();
21288    let hunks = snapshot
21289        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
21290        .map(|hunk| match hunk {
21291            DisplayDiffHunk::Unfolded {
21292                display_row_range, ..
21293            } => display_row_range,
21294            DisplayDiffHunk::Folded { .. } => unreachable!(),
21295        })
21296        .collect::<Vec<_>>();
21297    assert_eq!(
21298        hunks,
21299        [
21300            DisplayRow(2)..DisplayRow(4),
21301            DisplayRow(7)..DisplayRow(9),
21302            DisplayRow(12)..DisplayRow(14),
21303        ]
21304    );
21305}
21306
21307#[gpui::test]
21308async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
21309    init_test(cx, |_| {});
21310
21311    let mut cx = EditorTestContext::new(cx).await;
21312    cx.set_head_text(indoc! { "
21313        one
21314        two
21315        three
21316        four
21317        five
21318        "
21319    });
21320    cx.set_index_text(indoc! { "
21321        one
21322        two
21323        three
21324        four
21325        five
21326        "
21327    });
21328    cx.set_state(indoc! {"
21329        one
21330        TWO
21331        ˇTHREE
21332        FOUR
21333        five
21334    "});
21335    cx.run_until_parked();
21336    cx.update_editor(|editor, window, cx| {
21337        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21338    });
21339    cx.run_until_parked();
21340    cx.assert_index_text(Some(indoc! {"
21341        one
21342        TWO
21343        THREE
21344        FOUR
21345        five
21346    "}));
21347    cx.set_state(indoc! { "
21348        one
21349        TWO
21350        ˇTHREE-HUNDRED
21351        FOUR
21352        five
21353    "});
21354    cx.run_until_parked();
21355    cx.update_editor(|editor, window, cx| {
21356        let snapshot = editor.snapshot(window, cx);
21357        let hunks = editor
21358            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21359            .collect::<Vec<_>>();
21360        assert_eq!(hunks.len(), 1);
21361        assert_eq!(
21362            hunks[0].status(),
21363            DiffHunkStatus {
21364                kind: DiffHunkStatusKind::Modified,
21365                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
21366            }
21367        );
21368
21369        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21370    });
21371    cx.run_until_parked();
21372    cx.assert_index_text(Some(indoc! {"
21373        one
21374        TWO
21375        THREE-HUNDRED
21376        FOUR
21377        five
21378    "}));
21379}
21380
21381#[gpui::test]
21382fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
21383    init_test(cx, |_| {});
21384
21385    let editor = cx.add_window(|window, cx| {
21386        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
21387        build_editor(buffer, window, cx)
21388    });
21389
21390    let render_args = Arc::new(Mutex::new(None));
21391    let snapshot = editor
21392        .update(cx, |editor, window, cx| {
21393            let snapshot = editor.buffer().read(cx).snapshot(cx);
21394            let range =
21395                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
21396
21397            struct RenderArgs {
21398                row: MultiBufferRow,
21399                folded: bool,
21400                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
21401            }
21402
21403            let crease = Crease::inline(
21404                range,
21405                FoldPlaceholder::test(),
21406                {
21407                    let toggle_callback = render_args.clone();
21408                    move |row, folded, callback, _window, _cx| {
21409                        *toggle_callback.lock() = Some(RenderArgs {
21410                            row,
21411                            folded,
21412                            callback,
21413                        });
21414                        div()
21415                    }
21416                },
21417                |_row, _folded, _window, _cx| div(),
21418            );
21419
21420            editor.insert_creases(Some(crease), cx);
21421            let snapshot = editor.snapshot(window, cx);
21422            let _div =
21423                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
21424            snapshot
21425        })
21426        .unwrap();
21427
21428    let render_args = render_args.lock().take().unwrap();
21429    assert_eq!(render_args.row, MultiBufferRow(1));
21430    assert!(!render_args.folded);
21431    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21432
21433    cx.update_window(*editor, |_, window, cx| {
21434        (render_args.callback)(true, window, cx)
21435    })
21436    .unwrap();
21437    let snapshot = editor
21438        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21439        .unwrap();
21440    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
21441
21442    cx.update_window(*editor, |_, window, cx| {
21443        (render_args.callback)(false, window, cx)
21444    })
21445    .unwrap();
21446    let snapshot = editor
21447        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21448        .unwrap();
21449    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21450}
21451
21452#[gpui::test]
21453async fn test_input_text(cx: &mut TestAppContext) {
21454    init_test(cx, |_| {});
21455    let mut cx = EditorTestContext::new(cx).await;
21456
21457    cx.set_state(
21458        &r#"ˇone
21459        two
21460
21461        three
21462        fourˇ
21463        five
21464
21465        siˇx"#
21466            .unindent(),
21467    );
21468
21469    cx.dispatch_action(HandleInput(String::new()));
21470    cx.assert_editor_state(
21471        &r#"ˇone
21472        two
21473
21474        three
21475        fourˇ
21476        five
21477
21478        siˇx"#
21479            .unindent(),
21480    );
21481
21482    cx.dispatch_action(HandleInput("AAAA".to_string()));
21483    cx.assert_editor_state(
21484        &r#"AAAAˇone
21485        two
21486
21487        three
21488        fourAAAAˇ
21489        five
21490
21491        siAAAAˇx"#
21492            .unindent(),
21493    );
21494}
21495
21496#[gpui::test]
21497async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
21498    init_test(cx, |_| {});
21499
21500    let mut cx = EditorTestContext::new(cx).await;
21501    cx.set_state(
21502        r#"let foo = 1;
21503let foo = 2;
21504let foo = 3;
21505let fooˇ = 4;
21506let foo = 5;
21507let foo = 6;
21508let foo = 7;
21509let foo = 8;
21510let foo = 9;
21511let foo = 10;
21512let foo = 11;
21513let foo = 12;
21514let foo = 13;
21515let foo = 14;
21516let foo = 15;"#,
21517    );
21518
21519    cx.update_editor(|e, window, cx| {
21520        assert_eq!(
21521            e.next_scroll_position,
21522            NextScrollCursorCenterTopBottom::Center,
21523            "Default next scroll direction is center",
21524        );
21525
21526        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21527        assert_eq!(
21528            e.next_scroll_position,
21529            NextScrollCursorCenterTopBottom::Top,
21530            "After center, next scroll direction should be top",
21531        );
21532
21533        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21534        assert_eq!(
21535            e.next_scroll_position,
21536            NextScrollCursorCenterTopBottom::Bottom,
21537            "After top, next scroll direction should be bottom",
21538        );
21539
21540        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21541        assert_eq!(
21542            e.next_scroll_position,
21543            NextScrollCursorCenterTopBottom::Center,
21544            "After bottom, scrolling should start over",
21545        );
21546
21547        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21548        assert_eq!(
21549            e.next_scroll_position,
21550            NextScrollCursorCenterTopBottom::Top,
21551            "Scrolling continues if retriggered fast enough"
21552        );
21553    });
21554
21555    cx.executor()
21556        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
21557    cx.executor().run_until_parked();
21558    cx.update_editor(|e, _, _| {
21559        assert_eq!(
21560            e.next_scroll_position,
21561            NextScrollCursorCenterTopBottom::Center,
21562            "If scrolling is not triggered fast enough, it should reset"
21563        );
21564    });
21565}
21566
21567#[gpui::test]
21568async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
21569    init_test(cx, |_| {});
21570    let mut cx = EditorLspTestContext::new_rust(
21571        lsp::ServerCapabilities {
21572            definition_provider: Some(lsp::OneOf::Left(true)),
21573            references_provider: Some(lsp::OneOf::Left(true)),
21574            ..lsp::ServerCapabilities::default()
21575        },
21576        cx,
21577    )
21578    .await;
21579
21580    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
21581        let go_to_definition = cx
21582            .lsp
21583            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21584                move |params, _| async move {
21585                    if empty_go_to_definition {
21586                        Ok(None)
21587                    } else {
21588                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
21589                            uri: params.text_document_position_params.text_document.uri,
21590                            range: lsp::Range::new(
21591                                lsp::Position::new(4, 3),
21592                                lsp::Position::new(4, 6),
21593                            ),
21594                        })))
21595                    }
21596                },
21597            );
21598        let references = cx
21599            .lsp
21600            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21601                Ok(Some(vec![lsp::Location {
21602                    uri: params.text_document_position.text_document.uri,
21603                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
21604                }]))
21605            });
21606        (go_to_definition, references)
21607    };
21608
21609    cx.set_state(
21610        &r#"fn one() {
21611            let mut a = ˇtwo();
21612        }
21613
21614        fn two() {}"#
21615            .unindent(),
21616    );
21617    set_up_lsp_handlers(false, &mut cx);
21618    let navigated = cx
21619        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21620        .await
21621        .expect("Failed to navigate to definition");
21622    assert_eq!(
21623        navigated,
21624        Navigated::Yes,
21625        "Should have navigated to definition from the GetDefinition response"
21626    );
21627    cx.assert_editor_state(
21628        &r#"fn one() {
21629            let mut a = two();
21630        }
21631
21632        fn «twoˇ»() {}"#
21633            .unindent(),
21634    );
21635
21636    let editors = cx.update_workspace(|workspace, _, cx| {
21637        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21638    });
21639    cx.update_editor(|_, _, test_editor_cx| {
21640        assert_eq!(
21641            editors.len(),
21642            1,
21643            "Initially, only one, test, editor should be open in the workspace"
21644        );
21645        assert_eq!(
21646            test_editor_cx.entity(),
21647            editors.last().expect("Asserted len is 1").clone()
21648        );
21649    });
21650
21651    set_up_lsp_handlers(true, &mut cx);
21652    let navigated = cx
21653        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21654        .await
21655        .expect("Failed to navigate to lookup references");
21656    assert_eq!(
21657        navigated,
21658        Navigated::Yes,
21659        "Should have navigated to references as a fallback after empty GoToDefinition response"
21660    );
21661    // We should not change the selections in the existing file,
21662    // if opening another milti buffer with the references
21663    cx.assert_editor_state(
21664        &r#"fn one() {
21665            let mut a = two();
21666        }
21667
21668        fn «twoˇ»() {}"#
21669            .unindent(),
21670    );
21671    let editors = cx.update_workspace(|workspace, _, cx| {
21672        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21673    });
21674    cx.update_editor(|_, _, test_editor_cx| {
21675        assert_eq!(
21676            editors.len(),
21677            2,
21678            "After falling back to references search, we open a new editor with the results"
21679        );
21680        let references_fallback_text = editors
21681            .into_iter()
21682            .find(|new_editor| *new_editor != test_editor_cx.entity())
21683            .expect("Should have one non-test editor now")
21684            .read(test_editor_cx)
21685            .text(test_editor_cx);
21686        assert_eq!(
21687            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
21688            "Should use the range from the references response and not the GoToDefinition one"
21689        );
21690    });
21691}
21692
21693#[gpui::test]
21694async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
21695    init_test(cx, |_| {});
21696    cx.update(|cx| {
21697        let mut editor_settings = EditorSettings::get_global(cx).clone();
21698        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
21699        EditorSettings::override_global(editor_settings, cx);
21700    });
21701    let mut cx = EditorLspTestContext::new_rust(
21702        lsp::ServerCapabilities {
21703            definition_provider: Some(lsp::OneOf::Left(true)),
21704            references_provider: Some(lsp::OneOf::Left(true)),
21705            ..lsp::ServerCapabilities::default()
21706        },
21707        cx,
21708    )
21709    .await;
21710    let original_state = r#"fn one() {
21711        let mut a = ˇtwo();
21712    }
21713
21714    fn two() {}"#
21715        .unindent();
21716    cx.set_state(&original_state);
21717
21718    let mut go_to_definition = cx
21719        .lsp
21720        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21721            move |_, _| async move { Ok(None) },
21722        );
21723    let _references = cx
21724        .lsp
21725        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
21726            panic!("Should not call for references with no go to definition fallback")
21727        });
21728
21729    let navigated = cx
21730        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21731        .await
21732        .expect("Failed to navigate to lookup references");
21733    go_to_definition
21734        .next()
21735        .await
21736        .expect("Should have called the go_to_definition handler");
21737
21738    assert_eq!(
21739        navigated,
21740        Navigated::No,
21741        "Should have navigated to references as a fallback after empty GoToDefinition response"
21742    );
21743    cx.assert_editor_state(&original_state);
21744    let editors = cx.update_workspace(|workspace, _, cx| {
21745        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21746    });
21747    cx.update_editor(|_, _, _| {
21748        assert_eq!(
21749            editors.len(),
21750            1,
21751            "After unsuccessful fallback, no other editor should have been opened"
21752        );
21753    });
21754}
21755
21756#[gpui::test]
21757async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
21758    init_test(cx, |_| {});
21759    let mut cx = EditorLspTestContext::new_rust(
21760        lsp::ServerCapabilities {
21761            references_provider: Some(lsp::OneOf::Left(true)),
21762            ..lsp::ServerCapabilities::default()
21763        },
21764        cx,
21765    )
21766    .await;
21767
21768    cx.set_state(
21769        &r#"
21770        fn one() {
21771            let mut a = two();
21772        }
21773
21774        fn ˇtwo() {}"#
21775            .unindent(),
21776    );
21777    cx.lsp
21778        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21779            Ok(Some(vec![
21780                lsp::Location {
21781                    uri: params.text_document_position.text_document.uri.clone(),
21782                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21783                },
21784                lsp::Location {
21785                    uri: params.text_document_position.text_document.uri,
21786                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
21787                },
21788            ]))
21789        });
21790    let navigated = cx
21791        .update_editor(|editor, window, cx| {
21792            editor.find_all_references(&FindAllReferences, window, cx)
21793        })
21794        .unwrap()
21795        .await
21796        .expect("Failed to navigate to references");
21797    assert_eq!(
21798        navigated,
21799        Navigated::Yes,
21800        "Should have navigated to references from the FindAllReferences response"
21801    );
21802    cx.assert_editor_state(
21803        &r#"fn one() {
21804            let mut a = two();
21805        }
21806
21807        fn ˇtwo() {}"#
21808            .unindent(),
21809    );
21810
21811    let editors = cx.update_workspace(|workspace, _, cx| {
21812        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21813    });
21814    cx.update_editor(|_, _, _| {
21815        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
21816    });
21817
21818    cx.set_state(
21819        &r#"fn one() {
21820            let mut a = ˇtwo();
21821        }
21822
21823        fn two() {}"#
21824            .unindent(),
21825    );
21826    let navigated = cx
21827        .update_editor(|editor, window, cx| {
21828            editor.find_all_references(&FindAllReferences, window, cx)
21829        })
21830        .unwrap()
21831        .await
21832        .expect("Failed to navigate to references");
21833    assert_eq!(
21834        navigated,
21835        Navigated::Yes,
21836        "Should have navigated to references from the FindAllReferences response"
21837    );
21838    cx.assert_editor_state(
21839        &r#"fn one() {
21840            let mut a = ˇtwo();
21841        }
21842
21843        fn two() {}"#
21844            .unindent(),
21845    );
21846    let editors = cx.update_workspace(|workspace, _, cx| {
21847        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21848    });
21849    cx.update_editor(|_, _, _| {
21850        assert_eq!(
21851            editors.len(),
21852            2,
21853            "should have re-used the previous multibuffer"
21854        );
21855    });
21856
21857    cx.set_state(
21858        &r#"fn one() {
21859            let mut a = ˇtwo();
21860        }
21861        fn three() {}
21862        fn two() {}"#
21863            .unindent(),
21864    );
21865    cx.lsp
21866        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21867            Ok(Some(vec![
21868                lsp::Location {
21869                    uri: params.text_document_position.text_document.uri.clone(),
21870                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21871                },
21872                lsp::Location {
21873                    uri: params.text_document_position.text_document.uri,
21874                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
21875                },
21876            ]))
21877        });
21878    let navigated = cx
21879        .update_editor(|editor, window, cx| {
21880            editor.find_all_references(&FindAllReferences, window, cx)
21881        })
21882        .unwrap()
21883        .await
21884        .expect("Failed to navigate to references");
21885    assert_eq!(
21886        navigated,
21887        Navigated::Yes,
21888        "Should have navigated to references from the FindAllReferences response"
21889    );
21890    cx.assert_editor_state(
21891        &r#"fn one() {
21892                let mut a = ˇtwo();
21893            }
21894            fn three() {}
21895            fn two() {}"#
21896            .unindent(),
21897    );
21898    let editors = cx.update_workspace(|workspace, _, cx| {
21899        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21900    });
21901    cx.update_editor(|_, _, _| {
21902        assert_eq!(
21903            editors.len(),
21904            3,
21905            "should have used a new multibuffer as offsets changed"
21906        );
21907    });
21908}
21909#[gpui::test]
21910async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
21911    init_test(cx, |_| {});
21912
21913    let language = Arc::new(Language::new(
21914        LanguageConfig::default(),
21915        Some(tree_sitter_rust::LANGUAGE.into()),
21916    ));
21917
21918    let text = r#"
21919        #[cfg(test)]
21920        mod tests() {
21921            #[test]
21922            fn runnable_1() {
21923                let a = 1;
21924            }
21925
21926            #[test]
21927            fn runnable_2() {
21928                let a = 1;
21929                let b = 2;
21930            }
21931        }
21932    "#
21933    .unindent();
21934
21935    let fs = FakeFs::new(cx.executor());
21936    fs.insert_file("/file.rs", Default::default()).await;
21937
21938    let project = Project::test(fs, ["/a".as_ref()], cx).await;
21939    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21940    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21941    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
21942    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
21943
21944    let editor = cx.new_window_entity(|window, cx| {
21945        Editor::new(
21946            EditorMode::full(),
21947            multi_buffer,
21948            Some(project.clone()),
21949            window,
21950            cx,
21951        )
21952    });
21953
21954    editor.update_in(cx, |editor, window, cx| {
21955        let snapshot = editor.buffer().read(cx).snapshot(cx);
21956        editor.tasks.insert(
21957            (buffer.read(cx).remote_id(), 3),
21958            RunnableTasks {
21959                templates: vec![],
21960                offset: snapshot.anchor_before(43),
21961                column: 0,
21962                extra_variables: HashMap::default(),
21963                context_range: BufferOffset(43)..BufferOffset(85),
21964            },
21965        );
21966        editor.tasks.insert(
21967            (buffer.read(cx).remote_id(), 8),
21968            RunnableTasks {
21969                templates: vec![],
21970                offset: snapshot.anchor_before(86),
21971                column: 0,
21972                extra_variables: HashMap::default(),
21973                context_range: BufferOffset(86)..BufferOffset(191),
21974            },
21975        );
21976
21977        // Test finding task when cursor is inside function body
21978        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21979            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
21980        });
21981        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21982        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
21983
21984        // Test finding task when cursor is on function name
21985        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21986            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
21987        });
21988        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21989        assert_eq!(row, 8, "Should find task when cursor is on function name");
21990    });
21991}
21992
21993#[gpui::test]
21994async fn test_folding_buffers(cx: &mut TestAppContext) {
21995    init_test(cx, |_| {});
21996
21997    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
21998    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
21999    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
22000
22001    let fs = FakeFs::new(cx.executor());
22002    fs.insert_tree(
22003        path!("/a"),
22004        json!({
22005            "first.rs": sample_text_1,
22006            "second.rs": sample_text_2,
22007            "third.rs": sample_text_3,
22008        }),
22009    )
22010    .await;
22011    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22012    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22013    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22014    let worktree = project.update(cx, |project, cx| {
22015        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22016        assert_eq!(worktrees.len(), 1);
22017        worktrees.pop().unwrap()
22018    });
22019    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22020
22021    let buffer_1 = project
22022        .update(cx, |project, cx| {
22023            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
22024        })
22025        .await
22026        .unwrap();
22027    let buffer_2 = project
22028        .update(cx, |project, cx| {
22029            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
22030        })
22031        .await
22032        .unwrap();
22033    let buffer_3 = project
22034        .update(cx, |project, cx| {
22035            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
22036        })
22037        .await
22038        .unwrap();
22039
22040    let multi_buffer = cx.new(|cx| {
22041        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22042        multi_buffer.push_excerpts(
22043            buffer_1.clone(),
22044            [
22045                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22046                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22047                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22048            ],
22049            cx,
22050        );
22051        multi_buffer.push_excerpts(
22052            buffer_2.clone(),
22053            [
22054                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22055                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22056                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22057            ],
22058            cx,
22059        );
22060        multi_buffer.push_excerpts(
22061            buffer_3.clone(),
22062            [
22063                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22064                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22065                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22066            ],
22067            cx,
22068        );
22069        multi_buffer
22070    });
22071    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22072        Editor::new(
22073            EditorMode::full(),
22074            multi_buffer.clone(),
22075            Some(project.clone()),
22076            window,
22077            cx,
22078        )
22079    });
22080
22081    assert_eq!(
22082        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22083        "\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",
22084    );
22085
22086    multi_buffer_editor.update(cx, |editor, cx| {
22087        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
22088    });
22089    assert_eq!(
22090        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22091        "\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",
22092        "After folding the first buffer, its text should not be displayed"
22093    );
22094
22095    multi_buffer_editor.update(cx, |editor, cx| {
22096        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
22097    });
22098    assert_eq!(
22099        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22100        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
22101        "After folding the second buffer, its text should not be displayed"
22102    );
22103
22104    multi_buffer_editor.update(cx, |editor, cx| {
22105        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
22106    });
22107    assert_eq!(
22108        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22109        "\n\n\n\n\n",
22110        "After folding the third buffer, its text should not be displayed"
22111    );
22112
22113    // Emulate selection inside the fold logic, that should work
22114    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22115        editor
22116            .snapshot(window, cx)
22117            .next_line_boundary(Point::new(0, 4));
22118    });
22119
22120    multi_buffer_editor.update(cx, |editor, cx| {
22121        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
22122    });
22123    assert_eq!(
22124        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22125        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
22126        "After unfolding the second buffer, its text should be displayed"
22127    );
22128
22129    // Typing inside of buffer 1 causes that buffer to be unfolded.
22130    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22131        assert_eq!(
22132            multi_buffer
22133                .read(cx)
22134                .snapshot(cx)
22135                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
22136                .collect::<String>(),
22137            "bbbb"
22138        );
22139        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22140            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
22141        });
22142        editor.handle_input("B", window, cx);
22143    });
22144
22145    assert_eq!(
22146        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22147        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
22148        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
22149    );
22150
22151    multi_buffer_editor.update(cx, |editor, cx| {
22152        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
22153    });
22154    assert_eq!(
22155        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22156        "\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",
22157        "After unfolding the all buffers, all original text should be displayed"
22158    );
22159}
22160
22161#[gpui::test]
22162async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
22163    init_test(cx, |_| {});
22164
22165    let sample_text_1 = "1111\n2222\n3333".to_string();
22166    let sample_text_2 = "4444\n5555\n6666".to_string();
22167    let sample_text_3 = "7777\n8888\n9999".to_string();
22168
22169    let fs = FakeFs::new(cx.executor());
22170    fs.insert_tree(
22171        path!("/a"),
22172        json!({
22173            "first.rs": sample_text_1,
22174            "second.rs": sample_text_2,
22175            "third.rs": sample_text_3,
22176        }),
22177    )
22178    .await;
22179    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22180    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22181    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22182    let worktree = project.update(cx, |project, cx| {
22183        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22184        assert_eq!(worktrees.len(), 1);
22185        worktrees.pop().unwrap()
22186    });
22187    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22188
22189    let buffer_1 = project
22190        .update(cx, |project, cx| {
22191            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
22192        })
22193        .await
22194        .unwrap();
22195    let buffer_2 = project
22196        .update(cx, |project, cx| {
22197            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
22198        })
22199        .await
22200        .unwrap();
22201    let buffer_3 = project
22202        .update(cx, |project, cx| {
22203            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
22204        })
22205        .await
22206        .unwrap();
22207
22208    let multi_buffer = cx.new(|cx| {
22209        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22210        multi_buffer.push_excerpts(
22211            buffer_1.clone(),
22212            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22213            cx,
22214        );
22215        multi_buffer.push_excerpts(
22216            buffer_2.clone(),
22217            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22218            cx,
22219        );
22220        multi_buffer.push_excerpts(
22221            buffer_3.clone(),
22222            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22223            cx,
22224        );
22225        multi_buffer
22226    });
22227
22228    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22229        Editor::new(
22230            EditorMode::full(),
22231            multi_buffer,
22232            Some(project.clone()),
22233            window,
22234            cx,
22235        )
22236    });
22237
22238    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
22239    assert_eq!(
22240        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22241        full_text,
22242    );
22243
22244    multi_buffer_editor.update(cx, |editor, cx| {
22245        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
22246    });
22247    assert_eq!(
22248        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22249        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
22250        "After folding the first buffer, its text should not be displayed"
22251    );
22252
22253    multi_buffer_editor.update(cx, |editor, cx| {
22254        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
22255    });
22256
22257    assert_eq!(
22258        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22259        "\n\n\n\n\n\n7777\n8888\n9999",
22260        "After folding the second buffer, its text should not be displayed"
22261    );
22262
22263    multi_buffer_editor.update(cx, |editor, cx| {
22264        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
22265    });
22266    assert_eq!(
22267        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22268        "\n\n\n\n\n",
22269        "After folding the third buffer, its text should not be displayed"
22270    );
22271
22272    multi_buffer_editor.update(cx, |editor, cx| {
22273        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
22274    });
22275    assert_eq!(
22276        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22277        "\n\n\n\n4444\n5555\n6666\n\n",
22278        "After unfolding the second buffer, its text should be displayed"
22279    );
22280
22281    multi_buffer_editor.update(cx, |editor, cx| {
22282        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
22283    });
22284    assert_eq!(
22285        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22286        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
22287        "After unfolding the first buffer, its text should be displayed"
22288    );
22289
22290    multi_buffer_editor.update(cx, |editor, cx| {
22291        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
22292    });
22293    assert_eq!(
22294        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22295        full_text,
22296        "After unfolding all buffers, all original text should be displayed"
22297    );
22298}
22299
22300#[gpui::test]
22301async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
22302    init_test(cx, |_| {});
22303
22304    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
22305
22306    let fs = FakeFs::new(cx.executor());
22307    fs.insert_tree(
22308        path!("/a"),
22309        json!({
22310            "main.rs": sample_text,
22311        }),
22312    )
22313    .await;
22314    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22315    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22316    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22317    let worktree = project.update(cx, |project, cx| {
22318        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22319        assert_eq!(worktrees.len(), 1);
22320        worktrees.pop().unwrap()
22321    });
22322    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22323
22324    let buffer_1 = project
22325        .update(cx, |project, cx| {
22326            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22327        })
22328        .await
22329        .unwrap();
22330
22331    let multi_buffer = cx.new(|cx| {
22332        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22333        multi_buffer.push_excerpts(
22334            buffer_1.clone(),
22335            [ExcerptRange::new(
22336                Point::new(0, 0)
22337                    ..Point::new(
22338                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
22339                        0,
22340                    ),
22341            )],
22342            cx,
22343        );
22344        multi_buffer
22345    });
22346    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22347        Editor::new(
22348            EditorMode::full(),
22349            multi_buffer,
22350            Some(project.clone()),
22351            window,
22352            cx,
22353        )
22354    });
22355
22356    let selection_range = Point::new(1, 0)..Point::new(2, 0);
22357    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22358        enum TestHighlight {}
22359        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
22360        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
22361        editor.highlight_text::<TestHighlight>(
22362            vec![highlight_range.clone()],
22363            HighlightStyle::color(Hsla::green()),
22364            cx,
22365        );
22366        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22367            s.select_ranges(Some(highlight_range))
22368        });
22369    });
22370
22371    let full_text = format!("\n\n{sample_text}");
22372    assert_eq!(
22373        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22374        full_text,
22375    );
22376}
22377
22378#[gpui::test]
22379async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
22380    init_test(cx, |_| {});
22381    cx.update(|cx| {
22382        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
22383            "keymaps/default-linux.json",
22384            cx,
22385        )
22386        .unwrap();
22387        cx.bind_keys(default_key_bindings);
22388    });
22389
22390    let (editor, cx) = cx.add_window_view(|window, cx| {
22391        let multi_buffer = MultiBuffer::build_multi(
22392            [
22393                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
22394                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
22395                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
22396                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
22397            ],
22398            cx,
22399        );
22400        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
22401
22402        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
22403        // fold all but the second buffer, so that we test navigating between two
22404        // adjacent folded buffers, as well as folded buffers at the start and
22405        // end the multibuffer
22406        editor.fold_buffer(buffer_ids[0], cx);
22407        editor.fold_buffer(buffer_ids[2], cx);
22408        editor.fold_buffer(buffer_ids[3], cx);
22409
22410        editor
22411    });
22412    cx.simulate_resize(size(px(1000.), px(1000.)));
22413
22414    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
22415    cx.assert_excerpts_with_selections(indoc! {"
22416        [EXCERPT]
22417        ˇ[FOLDED]
22418        [EXCERPT]
22419        a1
22420        b1
22421        [EXCERPT]
22422        [FOLDED]
22423        [EXCERPT]
22424        [FOLDED]
22425        "
22426    });
22427    cx.simulate_keystroke("down");
22428    cx.assert_excerpts_with_selections(indoc! {"
22429        [EXCERPT]
22430        [FOLDED]
22431        [EXCERPT]
22432        ˇa1
22433        b1
22434        [EXCERPT]
22435        [FOLDED]
22436        [EXCERPT]
22437        [FOLDED]
22438        "
22439    });
22440    cx.simulate_keystroke("down");
22441    cx.assert_excerpts_with_selections(indoc! {"
22442        [EXCERPT]
22443        [FOLDED]
22444        [EXCERPT]
22445        a1
22446        ˇb1
22447        [EXCERPT]
22448        [FOLDED]
22449        [EXCERPT]
22450        [FOLDED]
22451        "
22452    });
22453    cx.simulate_keystroke("down");
22454    cx.assert_excerpts_with_selections(indoc! {"
22455        [EXCERPT]
22456        [FOLDED]
22457        [EXCERPT]
22458        a1
22459        b1
22460        ˇ[EXCERPT]
22461        [FOLDED]
22462        [EXCERPT]
22463        [FOLDED]
22464        "
22465    });
22466    cx.simulate_keystroke("down");
22467    cx.assert_excerpts_with_selections(indoc! {"
22468        [EXCERPT]
22469        [FOLDED]
22470        [EXCERPT]
22471        a1
22472        b1
22473        [EXCERPT]
22474        ˇ[FOLDED]
22475        [EXCERPT]
22476        [FOLDED]
22477        "
22478    });
22479    for _ in 0..5 {
22480        cx.simulate_keystroke("down");
22481        cx.assert_excerpts_with_selections(indoc! {"
22482            [EXCERPT]
22483            [FOLDED]
22484            [EXCERPT]
22485            a1
22486            b1
22487            [EXCERPT]
22488            [FOLDED]
22489            [EXCERPT]
22490            ˇ[FOLDED]
22491            "
22492        });
22493    }
22494
22495    cx.simulate_keystroke("up");
22496    cx.assert_excerpts_with_selections(indoc! {"
22497        [EXCERPT]
22498        [FOLDED]
22499        [EXCERPT]
22500        a1
22501        b1
22502        [EXCERPT]
22503        ˇ[FOLDED]
22504        [EXCERPT]
22505        [FOLDED]
22506        "
22507    });
22508    cx.simulate_keystroke("up");
22509    cx.assert_excerpts_with_selections(indoc! {"
22510        [EXCERPT]
22511        [FOLDED]
22512        [EXCERPT]
22513        a1
22514        b1
22515        ˇ[EXCERPT]
22516        [FOLDED]
22517        [EXCERPT]
22518        [FOLDED]
22519        "
22520    });
22521    cx.simulate_keystroke("up");
22522    cx.assert_excerpts_with_selections(indoc! {"
22523        [EXCERPT]
22524        [FOLDED]
22525        [EXCERPT]
22526        a1
22527        ˇb1
22528        [EXCERPT]
22529        [FOLDED]
22530        [EXCERPT]
22531        [FOLDED]
22532        "
22533    });
22534    cx.simulate_keystroke("up");
22535    cx.assert_excerpts_with_selections(indoc! {"
22536        [EXCERPT]
22537        [FOLDED]
22538        [EXCERPT]
22539        ˇa1
22540        b1
22541        [EXCERPT]
22542        [FOLDED]
22543        [EXCERPT]
22544        [FOLDED]
22545        "
22546    });
22547    for _ in 0..5 {
22548        cx.simulate_keystroke("up");
22549        cx.assert_excerpts_with_selections(indoc! {"
22550            [EXCERPT]
22551            ˇ[FOLDED]
22552            [EXCERPT]
22553            a1
22554            b1
22555            [EXCERPT]
22556            [FOLDED]
22557            [EXCERPT]
22558            [FOLDED]
22559            "
22560        });
22561    }
22562}
22563
22564#[gpui::test]
22565async fn test_edit_prediction_text(cx: &mut TestAppContext) {
22566    init_test(cx, |_| {});
22567
22568    // Simple insertion
22569    assert_highlighted_edits(
22570        "Hello, world!",
22571        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
22572        true,
22573        cx,
22574        |highlighted_edits, cx| {
22575            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
22576            assert_eq!(highlighted_edits.highlights.len(), 1);
22577            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
22578            assert_eq!(
22579                highlighted_edits.highlights[0].1.background_color,
22580                Some(cx.theme().status().created_background)
22581            );
22582        },
22583    )
22584    .await;
22585
22586    // Replacement
22587    assert_highlighted_edits(
22588        "This is a test.",
22589        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
22590        false,
22591        cx,
22592        |highlighted_edits, cx| {
22593            assert_eq!(highlighted_edits.text, "That is a test.");
22594            assert_eq!(highlighted_edits.highlights.len(), 1);
22595            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
22596            assert_eq!(
22597                highlighted_edits.highlights[0].1.background_color,
22598                Some(cx.theme().status().created_background)
22599            );
22600        },
22601    )
22602    .await;
22603
22604    // Multiple edits
22605    assert_highlighted_edits(
22606        "Hello, world!",
22607        vec![
22608            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
22609            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
22610        ],
22611        false,
22612        cx,
22613        |highlighted_edits, cx| {
22614            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
22615            assert_eq!(highlighted_edits.highlights.len(), 2);
22616            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
22617            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
22618            assert_eq!(
22619                highlighted_edits.highlights[0].1.background_color,
22620                Some(cx.theme().status().created_background)
22621            );
22622            assert_eq!(
22623                highlighted_edits.highlights[1].1.background_color,
22624                Some(cx.theme().status().created_background)
22625            );
22626        },
22627    )
22628    .await;
22629
22630    // Multiple lines with edits
22631    assert_highlighted_edits(
22632        "First line\nSecond line\nThird line\nFourth line",
22633        vec![
22634            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
22635            (
22636                Point::new(2, 0)..Point::new(2, 10),
22637                "New third line".to_string(),
22638            ),
22639            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
22640        ],
22641        false,
22642        cx,
22643        |highlighted_edits, cx| {
22644            assert_eq!(
22645                highlighted_edits.text,
22646                "Second modified\nNew third line\nFourth updated line"
22647            );
22648            assert_eq!(highlighted_edits.highlights.len(), 3);
22649            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
22650            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
22651            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
22652            for highlight in &highlighted_edits.highlights {
22653                assert_eq!(
22654                    highlight.1.background_color,
22655                    Some(cx.theme().status().created_background)
22656                );
22657            }
22658        },
22659    )
22660    .await;
22661}
22662
22663#[gpui::test]
22664async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
22665    init_test(cx, |_| {});
22666
22667    // Deletion
22668    assert_highlighted_edits(
22669        "Hello, world!",
22670        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
22671        true,
22672        cx,
22673        |highlighted_edits, cx| {
22674            assert_eq!(highlighted_edits.text, "Hello, world!");
22675            assert_eq!(highlighted_edits.highlights.len(), 1);
22676            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
22677            assert_eq!(
22678                highlighted_edits.highlights[0].1.background_color,
22679                Some(cx.theme().status().deleted_background)
22680            );
22681        },
22682    )
22683    .await;
22684
22685    // Insertion
22686    assert_highlighted_edits(
22687        "Hello, world!",
22688        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
22689        true,
22690        cx,
22691        |highlighted_edits, cx| {
22692            assert_eq!(highlighted_edits.highlights.len(), 1);
22693            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
22694            assert_eq!(
22695                highlighted_edits.highlights[0].1.background_color,
22696                Some(cx.theme().status().created_background)
22697            );
22698        },
22699    )
22700    .await;
22701}
22702
22703async fn assert_highlighted_edits(
22704    text: &str,
22705    edits: Vec<(Range<Point>, String)>,
22706    include_deletions: bool,
22707    cx: &mut TestAppContext,
22708    assertion_fn: impl Fn(HighlightedText, &App),
22709) {
22710    let window = cx.add_window(|window, cx| {
22711        let buffer = MultiBuffer::build_simple(text, cx);
22712        Editor::new(EditorMode::full(), buffer, None, window, cx)
22713    });
22714    let cx = &mut VisualTestContext::from_window(*window, cx);
22715
22716    let (buffer, snapshot) = window
22717        .update(cx, |editor, _window, cx| {
22718            (
22719                editor.buffer().clone(),
22720                editor.buffer().read(cx).snapshot(cx),
22721            )
22722        })
22723        .unwrap();
22724
22725    let edits = edits
22726        .into_iter()
22727        .map(|(range, edit)| {
22728            (
22729                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
22730                edit,
22731            )
22732        })
22733        .collect::<Vec<_>>();
22734
22735    let text_anchor_edits = edits
22736        .clone()
22737        .into_iter()
22738        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
22739        .collect::<Vec<_>>();
22740
22741    let edit_preview = window
22742        .update(cx, |_, _window, cx| {
22743            buffer
22744                .read(cx)
22745                .as_singleton()
22746                .unwrap()
22747                .read(cx)
22748                .preview_edits(text_anchor_edits.into(), cx)
22749        })
22750        .unwrap()
22751        .await;
22752
22753    cx.update(|_window, cx| {
22754        let highlighted_edits = edit_prediction_edit_text(
22755            snapshot.as_singleton().unwrap().2,
22756            &edits,
22757            &edit_preview,
22758            include_deletions,
22759            cx,
22760        );
22761        assertion_fn(highlighted_edits, cx)
22762    });
22763}
22764
22765#[track_caller]
22766fn assert_breakpoint(
22767    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
22768    path: &Arc<Path>,
22769    expected: Vec<(u32, Breakpoint)>,
22770) {
22771    if expected.is_empty() {
22772        assert!(!breakpoints.contains_key(path), "{}", path.display());
22773    } else {
22774        let mut breakpoint = breakpoints
22775            .get(path)
22776            .unwrap()
22777            .iter()
22778            .map(|breakpoint| {
22779                (
22780                    breakpoint.row,
22781                    Breakpoint {
22782                        message: breakpoint.message.clone(),
22783                        state: breakpoint.state,
22784                        condition: breakpoint.condition.clone(),
22785                        hit_condition: breakpoint.hit_condition.clone(),
22786                    },
22787                )
22788            })
22789            .collect::<Vec<_>>();
22790
22791        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
22792
22793        assert_eq!(expected, breakpoint);
22794    }
22795}
22796
22797fn add_log_breakpoint_at_cursor(
22798    editor: &mut Editor,
22799    log_message: &str,
22800    window: &mut Window,
22801    cx: &mut Context<Editor>,
22802) {
22803    let (anchor, bp) = editor
22804        .breakpoints_at_cursors(window, cx)
22805        .first()
22806        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
22807        .unwrap_or_else(|| {
22808            let snapshot = editor.snapshot(window, cx);
22809            let cursor_position: Point =
22810                editor.selections.newest(&snapshot.display_snapshot).head();
22811
22812            let breakpoint_position = snapshot
22813                .buffer_snapshot()
22814                .anchor_before(Point::new(cursor_position.row, 0));
22815
22816            (breakpoint_position, Breakpoint::new_log(log_message))
22817        });
22818
22819    editor.edit_breakpoint_at_anchor(
22820        anchor,
22821        bp,
22822        BreakpointEditAction::EditLogMessage(log_message.into()),
22823        cx,
22824    );
22825}
22826
22827#[gpui::test]
22828async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
22829    init_test(cx, |_| {});
22830
22831    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22832    let fs = FakeFs::new(cx.executor());
22833    fs.insert_tree(
22834        path!("/a"),
22835        json!({
22836            "main.rs": sample_text,
22837        }),
22838    )
22839    .await;
22840    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22841    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22842    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22843
22844    let fs = FakeFs::new(cx.executor());
22845    fs.insert_tree(
22846        path!("/a"),
22847        json!({
22848            "main.rs": sample_text,
22849        }),
22850    )
22851    .await;
22852    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22853    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22854    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22855    let worktree_id = workspace
22856        .update(cx, |workspace, _window, cx| {
22857            workspace.project().update(cx, |project, cx| {
22858                project.worktrees(cx).next().unwrap().read(cx).id()
22859            })
22860        })
22861        .unwrap();
22862
22863    let buffer = project
22864        .update(cx, |project, cx| {
22865            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22866        })
22867        .await
22868        .unwrap();
22869
22870    let (editor, cx) = cx.add_window_view(|window, cx| {
22871        Editor::new(
22872            EditorMode::full(),
22873            MultiBuffer::build_from_buffer(buffer, cx),
22874            Some(project.clone()),
22875            window,
22876            cx,
22877        )
22878    });
22879
22880    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22881    let abs_path = project.read_with(cx, |project, cx| {
22882        project
22883            .absolute_path(&project_path, cx)
22884            .map(Arc::from)
22885            .unwrap()
22886    });
22887
22888    // assert we can add breakpoint on the first line
22889    editor.update_in(cx, |editor, window, cx| {
22890        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22891        editor.move_to_end(&MoveToEnd, window, cx);
22892        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22893    });
22894
22895    let breakpoints = editor.update(cx, |editor, cx| {
22896        editor
22897            .breakpoint_store()
22898            .as_ref()
22899            .unwrap()
22900            .read(cx)
22901            .all_source_breakpoints(cx)
22902    });
22903
22904    assert_eq!(1, breakpoints.len());
22905    assert_breakpoint(
22906        &breakpoints,
22907        &abs_path,
22908        vec![
22909            (0, Breakpoint::new_standard()),
22910            (3, Breakpoint::new_standard()),
22911        ],
22912    );
22913
22914    editor.update_in(cx, |editor, window, cx| {
22915        editor.move_to_beginning(&MoveToBeginning, window, cx);
22916        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22917    });
22918
22919    let breakpoints = editor.update(cx, |editor, cx| {
22920        editor
22921            .breakpoint_store()
22922            .as_ref()
22923            .unwrap()
22924            .read(cx)
22925            .all_source_breakpoints(cx)
22926    });
22927
22928    assert_eq!(1, breakpoints.len());
22929    assert_breakpoint(
22930        &breakpoints,
22931        &abs_path,
22932        vec![(3, Breakpoint::new_standard())],
22933    );
22934
22935    editor.update_in(cx, |editor, window, cx| {
22936        editor.move_to_end(&MoveToEnd, window, cx);
22937        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22938    });
22939
22940    let breakpoints = editor.update(cx, |editor, cx| {
22941        editor
22942            .breakpoint_store()
22943            .as_ref()
22944            .unwrap()
22945            .read(cx)
22946            .all_source_breakpoints(cx)
22947    });
22948
22949    assert_eq!(0, breakpoints.len());
22950    assert_breakpoint(&breakpoints, &abs_path, vec![]);
22951}
22952
22953#[gpui::test]
22954async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
22955    init_test(cx, |_| {});
22956
22957    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22958
22959    let fs = FakeFs::new(cx.executor());
22960    fs.insert_tree(
22961        path!("/a"),
22962        json!({
22963            "main.rs": sample_text,
22964        }),
22965    )
22966    .await;
22967    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22968    let (workspace, cx) =
22969        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22970
22971    let worktree_id = workspace.update(cx, |workspace, cx| {
22972        workspace.project().update(cx, |project, cx| {
22973            project.worktrees(cx).next().unwrap().read(cx).id()
22974        })
22975    });
22976
22977    let buffer = project
22978        .update(cx, |project, cx| {
22979            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22980        })
22981        .await
22982        .unwrap();
22983
22984    let (editor, cx) = cx.add_window_view(|window, cx| {
22985        Editor::new(
22986            EditorMode::full(),
22987            MultiBuffer::build_from_buffer(buffer, cx),
22988            Some(project.clone()),
22989            window,
22990            cx,
22991        )
22992    });
22993
22994    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22995    let abs_path = project.read_with(cx, |project, cx| {
22996        project
22997            .absolute_path(&project_path, cx)
22998            .map(Arc::from)
22999            .unwrap()
23000    });
23001
23002    editor.update_in(cx, |editor, window, cx| {
23003        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
23004    });
23005
23006    let breakpoints = editor.update(cx, |editor, cx| {
23007        editor
23008            .breakpoint_store()
23009            .as_ref()
23010            .unwrap()
23011            .read(cx)
23012            .all_source_breakpoints(cx)
23013    });
23014
23015    assert_breakpoint(
23016        &breakpoints,
23017        &abs_path,
23018        vec![(0, Breakpoint::new_log("hello world"))],
23019    );
23020
23021    // Removing a log message from a log breakpoint should remove it
23022    editor.update_in(cx, |editor, window, cx| {
23023        add_log_breakpoint_at_cursor(editor, "", window, cx);
23024    });
23025
23026    let breakpoints = editor.update(cx, |editor, cx| {
23027        editor
23028            .breakpoint_store()
23029            .as_ref()
23030            .unwrap()
23031            .read(cx)
23032            .all_source_breakpoints(cx)
23033    });
23034
23035    assert_breakpoint(&breakpoints, &abs_path, vec![]);
23036
23037    editor.update_in(cx, |editor, window, cx| {
23038        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23039        editor.move_to_end(&MoveToEnd, window, cx);
23040        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23041        // Not adding a log message to a standard breakpoint shouldn't remove it
23042        add_log_breakpoint_at_cursor(editor, "", window, cx);
23043    });
23044
23045    let breakpoints = editor.update(cx, |editor, cx| {
23046        editor
23047            .breakpoint_store()
23048            .as_ref()
23049            .unwrap()
23050            .read(cx)
23051            .all_source_breakpoints(cx)
23052    });
23053
23054    assert_breakpoint(
23055        &breakpoints,
23056        &abs_path,
23057        vec![
23058            (0, Breakpoint::new_standard()),
23059            (3, Breakpoint::new_standard()),
23060        ],
23061    );
23062
23063    editor.update_in(cx, |editor, window, cx| {
23064        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
23065    });
23066
23067    let breakpoints = editor.update(cx, |editor, cx| {
23068        editor
23069            .breakpoint_store()
23070            .as_ref()
23071            .unwrap()
23072            .read(cx)
23073            .all_source_breakpoints(cx)
23074    });
23075
23076    assert_breakpoint(
23077        &breakpoints,
23078        &abs_path,
23079        vec![
23080            (0, Breakpoint::new_standard()),
23081            (3, Breakpoint::new_log("hello world")),
23082        ],
23083    );
23084
23085    editor.update_in(cx, |editor, window, cx| {
23086        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
23087    });
23088
23089    let breakpoints = editor.update(cx, |editor, cx| {
23090        editor
23091            .breakpoint_store()
23092            .as_ref()
23093            .unwrap()
23094            .read(cx)
23095            .all_source_breakpoints(cx)
23096    });
23097
23098    assert_breakpoint(
23099        &breakpoints,
23100        &abs_path,
23101        vec![
23102            (0, Breakpoint::new_standard()),
23103            (3, Breakpoint::new_log("hello Earth!!")),
23104        ],
23105    );
23106}
23107
23108/// This also tests that Editor::breakpoint_at_cursor_head is working properly
23109/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
23110/// or when breakpoints were placed out of order. This tests for a regression too
23111#[gpui::test]
23112async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
23113    init_test(cx, |_| {});
23114
23115    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23116    let fs = FakeFs::new(cx.executor());
23117    fs.insert_tree(
23118        path!("/a"),
23119        json!({
23120            "main.rs": sample_text,
23121        }),
23122    )
23123    .await;
23124    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23125    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23126    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23127
23128    let fs = FakeFs::new(cx.executor());
23129    fs.insert_tree(
23130        path!("/a"),
23131        json!({
23132            "main.rs": sample_text,
23133        }),
23134    )
23135    .await;
23136    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23137    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23138    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23139    let worktree_id = workspace
23140        .update(cx, |workspace, _window, cx| {
23141            workspace.project().update(cx, |project, cx| {
23142                project.worktrees(cx).next().unwrap().read(cx).id()
23143            })
23144        })
23145        .unwrap();
23146
23147    let buffer = project
23148        .update(cx, |project, cx| {
23149            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23150        })
23151        .await
23152        .unwrap();
23153
23154    let (editor, cx) = cx.add_window_view(|window, cx| {
23155        Editor::new(
23156            EditorMode::full(),
23157            MultiBuffer::build_from_buffer(buffer, cx),
23158            Some(project.clone()),
23159            window,
23160            cx,
23161        )
23162    });
23163
23164    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23165    let abs_path = project.read_with(cx, |project, cx| {
23166        project
23167            .absolute_path(&project_path, cx)
23168            .map(Arc::from)
23169            .unwrap()
23170    });
23171
23172    // assert we can add breakpoint on the first line
23173    editor.update_in(cx, |editor, window, cx| {
23174        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23175        editor.move_to_end(&MoveToEnd, window, cx);
23176        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23177        editor.move_up(&MoveUp, window, cx);
23178        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23179    });
23180
23181    let breakpoints = editor.update(cx, |editor, cx| {
23182        editor
23183            .breakpoint_store()
23184            .as_ref()
23185            .unwrap()
23186            .read(cx)
23187            .all_source_breakpoints(cx)
23188    });
23189
23190    assert_eq!(1, breakpoints.len());
23191    assert_breakpoint(
23192        &breakpoints,
23193        &abs_path,
23194        vec![
23195            (0, Breakpoint::new_standard()),
23196            (2, Breakpoint::new_standard()),
23197            (3, Breakpoint::new_standard()),
23198        ],
23199    );
23200
23201    editor.update_in(cx, |editor, window, cx| {
23202        editor.move_to_beginning(&MoveToBeginning, window, cx);
23203        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23204        editor.move_to_end(&MoveToEnd, window, cx);
23205        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23206        // Disabling a breakpoint that doesn't exist should do nothing
23207        editor.move_up(&MoveUp, window, cx);
23208        editor.move_up(&MoveUp, window, cx);
23209        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23210    });
23211
23212    let breakpoints = editor.update(cx, |editor, cx| {
23213        editor
23214            .breakpoint_store()
23215            .as_ref()
23216            .unwrap()
23217            .read(cx)
23218            .all_source_breakpoints(cx)
23219    });
23220
23221    let disable_breakpoint = {
23222        let mut bp = Breakpoint::new_standard();
23223        bp.state = BreakpointState::Disabled;
23224        bp
23225    };
23226
23227    assert_eq!(1, breakpoints.len());
23228    assert_breakpoint(
23229        &breakpoints,
23230        &abs_path,
23231        vec![
23232            (0, disable_breakpoint.clone()),
23233            (2, Breakpoint::new_standard()),
23234            (3, disable_breakpoint.clone()),
23235        ],
23236    );
23237
23238    editor.update_in(cx, |editor, window, cx| {
23239        editor.move_to_beginning(&MoveToBeginning, window, cx);
23240        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23241        editor.move_to_end(&MoveToEnd, window, cx);
23242        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23243        editor.move_up(&MoveUp, window, cx);
23244        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23245    });
23246
23247    let breakpoints = editor.update(cx, |editor, cx| {
23248        editor
23249            .breakpoint_store()
23250            .as_ref()
23251            .unwrap()
23252            .read(cx)
23253            .all_source_breakpoints(cx)
23254    });
23255
23256    assert_eq!(1, breakpoints.len());
23257    assert_breakpoint(
23258        &breakpoints,
23259        &abs_path,
23260        vec![
23261            (0, Breakpoint::new_standard()),
23262            (2, disable_breakpoint),
23263            (3, Breakpoint::new_standard()),
23264        ],
23265    );
23266}
23267
23268#[gpui::test]
23269async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
23270    init_test(cx, |_| {});
23271    let capabilities = lsp::ServerCapabilities {
23272        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
23273            prepare_provider: Some(true),
23274            work_done_progress_options: Default::default(),
23275        })),
23276        ..Default::default()
23277    };
23278    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23279
23280    cx.set_state(indoc! {"
23281        struct Fˇoo {}
23282    "});
23283
23284    cx.update_editor(|editor, _, cx| {
23285        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23286        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23287        editor.highlight_background::<DocumentHighlightRead>(
23288            &[highlight_range],
23289            |theme| theme.colors().editor_document_highlight_read_background,
23290            cx,
23291        );
23292    });
23293
23294    let mut prepare_rename_handler = cx
23295        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
23296            move |_, _, _| async move {
23297                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
23298                    start: lsp::Position {
23299                        line: 0,
23300                        character: 7,
23301                    },
23302                    end: lsp::Position {
23303                        line: 0,
23304                        character: 10,
23305                    },
23306                })))
23307            },
23308        );
23309    let prepare_rename_task = cx
23310        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23311        .expect("Prepare rename was not started");
23312    prepare_rename_handler.next().await.unwrap();
23313    prepare_rename_task.await.expect("Prepare rename failed");
23314
23315    let mut rename_handler =
23316        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23317            let edit = lsp::TextEdit {
23318                range: lsp::Range {
23319                    start: lsp::Position {
23320                        line: 0,
23321                        character: 7,
23322                    },
23323                    end: lsp::Position {
23324                        line: 0,
23325                        character: 10,
23326                    },
23327                },
23328                new_text: "FooRenamed".to_string(),
23329            };
23330            Ok(Some(lsp::WorkspaceEdit::new(
23331                // Specify the same edit twice
23332                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
23333            )))
23334        });
23335    let rename_task = cx
23336        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23337        .expect("Confirm rename was not started");
23338    rename_handler.next().await.unwrap();
23339    rename_task.await.expect("Confirm rename failed");
23340    cx.run_until_parked();
23341
23342    // Despite two edits, only one is actually applied as those are identical
23343    cx.assert_editor_state(indoc! {"
23344        struct FooRenamedˇ {}
23345    "});
23346}
23347
23348#[gpui::test]
23349async fn test_rename_without_prepare(cx: &mut TestAppContext) {
23350    init_test(cx, |_| {});
23351    // These capabilities indicate that the server does not support prepare rename.
23352    let capabilities = lsp::ServerCapabilities {
23353        rename_provider: Some(lsp::OneOf::Left(true)),
23354        ..Default::default()
23355    };
23356    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23357
23358    cx.set_state(indoc! {"
23359        struct Fˇoo {}
23360    "});
23361
23362    cx.update_editor(|editor, _window, cx| {
23363        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23364        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23365        editor.highlight_background::<DocumentHighlightRead>(
23366            &[highlight_range],
23367            |theme| theme.colors().editor_document_highlight_read_background,
23368            cx,
23369        );
23370    });
23371
23372    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23373        .expect("Prepare rename was not started")
23374        .await
23375        .expect("Prepare rename failed");
23376
23377    let mut rename_handler =
23378        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23379            let edit = lsp::TextEdit {
23380                range: lsp::Range {
23381                    start: lsp::Position {
23382                        line: 0,
23383                        character: 7,
23384                    },
23385                    end: lsp::Position {
23386                        line: 0,
23387                        character: 10,
23388                    },
23389                },
23390                new_text: "FooRenamed".to_string(),
23391            };
23392            Ok(Some(lsp::WorkspaceEdit::new(
23393                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
23394            )))
23395        });
23396    let rename_task = cx
23397        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23398        .expect("Confirm rename was not started");
23399    rename_handler.next().await.unwrap();
23400    rename_task.await.expect("Confirm rename failed");
23401    cx.run_until_parked();
23402
23403    // Correct range is renamed, as `surrounding_word` is used to find it.
23404    cx.assert_editor_state(indoc! {"
23405        struct FooRenamedˇ {}
23406    "});
23407}
23408
23409#[gpui::test]
23410async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
23411    init_test(cx, |_| {});
23412    let mut cx = EditorTestContext::new(cx).await;
23413
23414    let language = Arc::new(
23415        Language::new(
23416            LanguageConfig::default(),
23417            Some(tree_sitter_html::LANGUAGE.into()),
23418        )
23419        .with_brackets_query(
23420            r#"
23421            ("<" @open "/>" @close)
23422            ("</" @open ">" @close)
23423            ("<" @open ">" @close)
23424            ("\"" @open "\"" @close)
23425            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
23426        "#,
23427        )
23428        .unwrap(),
23429    );
23430    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23431
23432    cx.set_state(indoc! {"
23433        <span>ˇ</span>
23434    "});
23435    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23436    cx.assert_editor_state(indoc! {"
23437        <span>
23438        ˇ
23439        </span>
23440    "});
23441
23442    cx.set_state(indoc! {"
23443        <span><span></span>ˇ</span>
23444    "});
23445    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23446    cx.assert_editor_state(indoc! {"
23447        <span><span></span>
23448        ˇ</span>
23449    "});
23450
23451    cx.set_state(indoc! {"
23452        <span>ˇ
23453        </span>
23454    "});
23455    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23456    cx.assert_editor_state(indoc! {"
23457        <span>
23458        ˇ
23459        </span>
23460    "});
23461}
23462
23463#[gpui::test(iterations = 10)]
23464async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
23465    init_test(cx, |_| {});
23466
23467    let fs = FakeFs::new(cx.executor());
23468    fs.insert_tree(
23469        path!("/dir"),
23470        json!({
23471            "a.ts": "a",
23472        }),
23473    )
23474    .await;
23475
23476    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
23477    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23478    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23479
23480    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23481    language_registry.add(Arc::new(Language::new(
23482        LanguageConfig {
23483            name: "TypeScript".into(),
23484            matcher: LanguageMatcher {
23485                path_suffixes: vec!["ts".to_string()],
23486                ..Default::default()
23487            },
23488            ..Default::default()
23489        },
23490        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
23491    )));
23492    let mut fake_language_servers = language_registry.register_fake_lsp(
23493        "TypeScript",
23494        FakeLspAdapter {
23495            capabilities: lsp::ServerCapabilities {
23496                code_lens_provider: Some(lsp::CodeLensOptions {
23497                    resolve_provider: Some(true),
23498                }),
23499                execute_command_provider: Some(lsp::ExecuteCommandOptions {
23500                    commands: vec!["_the/command".to_string()],
23501                    ..lsp::ExecuteCommandOptions::default()
23502                }),
23503                ..lsp::ServerCapabilities::default()
23504            },
23505            ..FakeLspAdapter::default()
23506        },
23507    );
23508
23509    let editor = workspace
23510        .update(cx, |workspace, window, cx| {
23511            workspace.open_abs_path(
23512                PathBuf::from(path!("/dir/a.ts")),
23513                OpenOptions::default(),
23514                window,
23515                cx,
23516            )
23517        })
23518        .unwrap()
23519        .await
23520        .unwrap()
23521        .downcast::<Editor>()
23522        .unwrap();
23523    cx.executor().run_until_parked();
23524
23525    let fake_server = fake_language_servers.next().await.unwrap();
23526
23527    let buffer = editor.update(cx, |editor, cx| {
23528        editor
23529            .buffer()
23530            .read(cx)
23531            .as_singleton()
23532            .expect("have opened a single file by path")
23533    });
23534
23535    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
23536    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
23537    drop(buffer_snapshot);
23538    let actions = cx
23539        .update_window(*workspace, |_, window, cx| {
23540            project.code_actions(&buffer, anchor..anchor, window, cx)
23541        })
23542        .unwrap();
23543
23544    fake_server
23545        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23546            Ok(Some(vec![
23547                lsp::CodeLens {
23548                    range: lsp::Range::default(),
23549                    command: Some(lsp::Command {
23550                        title: "Code lens command".to_owned(),
23551                        command: "_the/command".to_owned(),
23552                        arguments: None,
23553                    }),
23554                    data: None,
23555                },
23556                lsp::CodeLens {
23557                    range: lsp::Range::default(),
23558                    command: Some(lsp::Command {
23559                        title: "Command not in capabilities".to_owned(),
23560                        command: "not in capabilities".to_owned(),
23561                        arguments: None,
23562                    }),
23563                    data: None,
23564                },
23565                lsp::CodeLens {
23566                    range: lsp::Range {
23567                        start: lsp::Position {
23568                            line: 1,
23569                            character: 1,
23570                        },
23571                        end: lsp::Position {
23572                            line: 1,
23573                            character: 1,
23574                        },
23575                    },
23576                    command: Some(lsp::Command {
23577                        title: "Command not in range".to_owned(),
23578                        command: "_the/command".to_owned(),
23579                        arguments: None,
23580                    }),
23581                    data: None,
23582                },
23583            ]))
23584        })
23585        .next()
23586        .await;
23587
23588    let actions = actions.await.unwrap();
23589    assert_eq!(
23590        actions.len(),
23591        1,
23592        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
23593    );
23594    let action = actions[0].clone();
23595    let apply = project.update(cx, |project, cx| {
23596        project.apply_code_action(buffer.clone(), action, true, cx)
23597    });
23598
23599    // Resolving the code action does not populate its edits. In absence of
23600    // edits, we must execute the given command.
23601    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
23602        |mut lens, _| async move {
23603            let lens_command = lens.command.as_mut().expect("should have a command");
23604            assert_eq!(lens_command.title, "Code lens command");
23605            lens_command.arguments = Some(vec![json!("the-argument")]);
23606            Ok(lens)
23607        },
23608    );
23609
23610    // While executing the command, the language server sends the editor
23611    // a `workspaceEdit` request.
23612    fake_server
23613        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
23614            let fake = fake_server.clone();
23615            move |params, _| {
23616                assert_eq!(params.command, "_the/command");
23617                let fake = fake.clone();
23618                async move {
23619                    fake.server
23620                        .request::<lsp::request::ApplyWorkspaceEdit>(
23621                            lsp::ApplyWorkspaceEditParams {
23622                                label: None,
23623                                edit: lsp::WorkspaceEdit {
23624                                    changes: Some(
23625                                        [(
23626                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
23627                                            vec![lsp::TextEdit {
23628                                                range: lsp::Range::new(
23629                                                    lsp::Position::new(0, 0),
23630                                                    lsp::Position::new(0, 0),
23631                                                ),
23632                                                new_text: "X".into(),
23633                                            }],
23634                                        )]
23635                                        .into_iter()
23636                                        .collect(),
23637                                    ),
23638                                    ..lsp::WorkspaceEdit::default()
23639                                },
23640                            },
23641                        )
23642                        .await
23643                        .into_response()
23644                        .unwrap();
23645                    Ok(Some(json!(null)))
23646                }
23647            }
23648        })
23649        .next()
23650        .await;
23651
23652    // Applying the code lens command returns a project transaction containing the edits
23653    // sent by the language server in its `workspaceEdit` request.
23654    let transaction = apply.await.unwrap();
23655    assert!(transaction.0.contains_key(&buffer));
23656    buffer.update(cx, |buffer, cx| {
23657        assert_eq!(buffer.text(), "Xa");
23658        buffer.undo(cx);
23659        assert_eq!(buffer.text(), "a");
23660    });
23661
23662    let actions_after_edits = cx
23663        .update_window(*workspace, |_, window, cx| {
23664            project.code_actions(&buffer, anchor..anchor, window, cx)
23665        })
23666        .unwrap()
23667        .await
23668        .unwrap();
23669    assert_eq!(
23670        actions, actions_after_edits,
23671        "For the same selection, same code lens actions should be returned"
23672    );
23673
23674    let _responses =
23675        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23676            panic!("No more code lens requests are expected");
23677        });
23678    editor.update_in(cx, |editor, window, cx| {
23679        editor.select_all(&SelectAll, window, cx);
23680    });
23681    cx.executor().run_until_parked();
23682    let new_actions = cx
23683        .update_window(*workspace, |_, window, cx| {
23684            project.code_actions(&buffer, anchor..anchor, window, cx)
23685        })
23686        .unwrap()
23687        .await
23688        .unwrap();
23689    assert_eq!(
23690        actions, new_actions,
23691        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
23692    );
23693}
23694
23695#[gpui::test]
23696async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
23697    init_test(cx, |_| {});
23698
23699    let fs = FakeFs::new(cx.executor());
23700    let main_text = r#"fn main() {
23701println!("1");
23702println!("2");
23703println!("3");
23704println!("4");
23705println!("5");
23706}"#;
23707    let lib_text = "mod foo {}";
23708    fs.insert_tree(
23709        path!("/a"),
23710        json!({
23711            "lib.rs": lib_text,
23712            "main.rs": main_text,
23713        }),
23714    )
23715    .await;
23716
23717    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23718    let (workspace, cx) =
23719        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23720    let worktree_id = workspace.update(cx, |workspace, cx| {
23721        workspace.project().update(cx, |project, cx| {
23722            project.worktrees(cx).next().unwrap().read(cx).id()
23723        })
23724    });
23725
23726    let expected_ranges = vec![
23727        Point::new(0, 0)..Point::new(0, 0),
23728        Point::new(1, 0)..Point::new(1, 1),
23729        Point::new(2, 0)..Point::new(2, 2),
23730        Point::new(3, 0)..Point::new(3, 3),
23731    ];
23732
23733    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23734    let editor_1 = workspace
23735        .update_in(cx, |workspace, window, cx| {
23736            workspace.open_path(
23737                (worktree_id, rel_path("main.rs")),
23738                Some(pane_1.downgrade()),
23739                true,
23740                window,
23741                cx,
23742            )
23743        })
23744        .unwrap()
23745        .await
23746        .downcast::<Editor>()
23747        .unwrap();
23748    pane_1.update(cx, |pane, cx| {
23749        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23750        open_editor.update(cx, |editor, cx| {
23751            assert_eq!(
23752                editor.display_text(cx),
23753                main_text,
23754                "Original main.rs text on initial open",
23755            );
23756            assert_eq!(
23757                editor
23758                    .selections
23759                    .all::<Point>(&editor.display_snapshot(cx))
23760                    .into_iter()
23761                    .map(|s| s.range())
23762                    .collect::<Vec<_>>(),
23763                vec![Point::zero()..Point::zero()],
23764                "Default selections on initial open",
23765            );
23766        })
23767    });
23768    editor_1.update_in(cx, |editor, window, cx| {
23769        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23770            s.select_ranges(expected_ranges.clone());
23771        });
23772    });
23773
23774    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
23775        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
23776    });
23777    let editor_2 = workspace
23778        .update_in(cx, |workspace, window, cx| {
23779            workspace.open_path(
23780                (worktree_id, rel_path("main.rs")),
23781                Some(pane_2.downgrade()),
23782                true,
23783                window,
23784                cx,
23785            )
23786        })
23787        .unwrap()
23788        .await
23789        .downcast::<Editor>()
23790        .unwrap();
23791    pane_2.update(cx, |pane, cx| {
23792        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23793        open_editor.update(cx, |editor, cx| {
23794            assert_eq!(
23795                editor.display_text(cx),
23796                main_text,
23797                "Original main.rs text on initial open in another panel",
23798            );
23799            assert_eq!(
23800                editor
23801                    .selections
23802                    .all::<Point>(&editor.display_snapshot(cx))
23803                    .into_iter()
23804                    .map(|s| s.range())
23805                    .collect::<Vec<_>>(),
23806                vec![Point::zero()..Point::zero()],
23807                "Default selections on initial open in another panel",
23808            );
23809        })
23810    });
23811
23812    editor_2.update_in(cx, |editor, window, cx| {
23813        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
23814    });
23815
23816    let _other_editor_1 = workspace
23817        .update_in(cx, |workspace, window, cx| {
23818            workspace.open_path(
23819                (worktree_id, rel_path("lib.rs")),
23820                Some(pane_1.downgrade()),
23821                true,
23822                window,
23823                cx,
23824            )
23825        })
23826        .unwrap()
23827        .await
23828        .downcast::<Editor>()
23829        .unwrap();
23830    pane_1
23831        .update_in(cx, |pane, window, cx| {
23832            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23833        })
23834        .await
23835        .unwrap();
23836    drop(editor_1);
23837    pane_1.update(cx, |pane, cx| {
23838        pane.active_item()
23839            .unwrap()
23840            .downcast::<Editor>()
23841            .unwrap()
23842            .update(cx, |editor, cx| {
23843                assert_eq!(
23844                    editor.display_text(cx),
23845                    lib_text,
23846                    "Other file should be open and active",
23847                );
23848            });
23849        assert_eq!(pane.items().count(), 1, "No other editors should be open");
23850    });
23851
23852    let _other_editor_2 = workspace
23853        .update_in(cx, |workspace, window, cx| {
23854            workspace.open_path(
23855                (worktree_id, rel_path("lib.rs")),
23856                Some(pane_2.downgrade()),
23857                true,
23858                window,
23859                cx,
23860            )
23861        })
23862        .unwrap()
23863        .await
23864        .downcast::<Editor>()
23865        .unwrap();
23866    pane_2
23867        .update_in(cx, |pane, window, cx| {
23868            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23869        })
23870        .await
23871        .unwrap();
23872    drop(editor_2);
23873    pane_2.update(cx, |pane, cx| {
23874        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23875        open_editor.update(cx, |editor, cx| {
23876            assert_eq!(
23877                editor.display_text(cx),
23878                lib_text,
23879                "Other file should be open and active in another panel too",
23880            );
23881        });
23882        assert_eq!(
23883            pane.items().count(),
23884            1,
23885            "No other editors should be open in another pane",
23886        );
23887    });
23888
23889    let _editor_1_reopened = workspace
23890        .update_in(cx, |workspace, window, cx| {
23891            workspace.open_path(
23892                (worktree_id, rel_path("main.rs")),
23893                Some(pane_1.downgrade()),
23894                true,
23895                window,
23896                cx,
23897            )
23898        })
23899        .unwrap()
23900        .await
23901        .downcast::<Editor>()
23902        .unwrap();
23903    let _editor_2_reopened = workspace
23904        .update_in(cx, |workspace, window, cx| {
23905            workspace.open_path(
23906                (worktree_id, rel_path("main.rs")),
23907                Some(pane_2.downgrade()),
23908                true,
23909                window,
23910                cx,
23911            )
23912        })
23913        .unwrap()
23914        .await
23915        .downcast::<Editor>()
23916        .unwrap();
23917    pane_1.update(cx, |pane, cx| {
23918        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23919        open_editor.update(cx, |editor, cx| {
23920            assert_eq!(
23921                editor.display_text(cx),
23922                main_text,
23923                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
23924            );
23925            assert_eq!(
23926                editor
23927                    .selections
23928                    .all::<Point>(&editor.display_snapshot(cx))
23929                    .into_iter()
23930                    .map(|s| s.range())
23931                    .collect::<Vec<_>>(),
23932                expected_ranges,
23933                "Previous editor in the 1st panel had selections and should get them restored on reopen",
23934            );
23935        })
23936    });
23937    pane_2.update(cx, |pane, cx| {
23938        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23939        open_editor.update(cx, |editor, cx| {
23940            assert_eq!(
23941                editor.display_text(cx),
23942                r#"fn main() {
23943⋯rintln!("1");
23944⋯intln!("2");
23945⋯ntln!("3");
23946println!("4");
23947println!("5");
23948}"#,
23949                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
23950            );
23951            assert_eq!(
23952                editor
23953                    .selections
23954                    .all::<Point>(&editor.display_snapshot(cx))
23955                    .into_iter()
23956                    .map(|s| s.range())
23957                    .collect::<Vec<_>>(),
23958                vec![Point::zero()..Point::zero()],
23959                "Previous editor in the 2nd pane had no selections changed hence should restore none",
23960            );
23961        })
23962    });
23963}
23964
23965#[gpui::test]
23966async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
23967    init_test(cx, |_| {});
23968
23969    let fs = FakeFs::new(cx.executor());
23970    let main_text = r#"fn main() {
23971println!("1");
23972println!("2");
23973println!("3");
23974println!("4");
23975println!("5");
23976}"#;
23977    let lib_text = "mod foo {}";
23978    fs.insert_tree(
23979        path!("/a"),
23980        json!({
23981            "lib.rs": lib_text,
23982            "main.rs": main_text,
23983        }),
23984    )
23985    .await;
23986
23987    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23988    let (workspace, cx) =
23989        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23990    let worktree_id = workspace.update(cx, |workspace, cx| {
23991        workspace.project().update(cx, |project, cx| {
23992            project.worktrees(cx).next().unwrap().read(cx).id()
23993        })
23994    });
23995
23996    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23997    let editor = workspace
23998        .update_in(cx, |workspace, window, cx| {
23999            workspace.open_path(
24000                (worktree_id, rel_path("main.rs")),
24001                Some(pane.downgrade()),
24002                true,
24003                window,
24004                cx,
24005            )
24006        })
24007        .unwrap()
24008        .await
24009        .downcast::<Editor>()
24010        .unwrap();
24011    pane.update(cx, |pane, cx| {
24012        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24013        open_editor.update(cx, |editor, cx| {
24014            assert_eq!(
24015                editor.display_text(cx),
24016                main_text,
24017                "Original main.rs text on initial open",
24018            );
24019        })
24020    });
24021    editor.update_in(cx, |editor, window, cx| {
24022        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
24023    });
24024
24025    cx.update_global(|store: &mut SettingsStore, cx| {
24026        store.update_user_settings(cx, |s| {
24027            s.workspace.restore_on_file_reopen = Some(false);
24028        });
24029    });
24030    editor.update_in(cx, |editor, window, cx| {
24031        editor.fold_ranges(
24032            vec![
24033                Point::new(1, 0)..Point::new(1, 1),
24034                Point::new(2, 0)..Point::new(2, 2),
24035                Point::new(3, 0)..Point::new(3, 3),
24036            ],
24037            false,
24038            window,
24039            cx,
24040        );
24041    });
24042    pane.update_in(cx, |pane, window, cx| {
24043        pane.close_all_items(&CloseAllItems::default(), window, cx)
24044    })
24045    .await
24046    .unwrap();
24047    pane.update(cx, |pane, _| {
24048        assert!(pane.active_item().is_none());
24049    });
24050    cx.update_global(|store: &mut SettingsStore, cx| {
24051        store.update_user_settings(cx, |s| {
24052            s.workspace.restore_on_file_reopen = Some(true);
24053        });
24054    });
24055
24056    let _editor_reopened = workspace
24057        .update_in(cx, |workspace, window, cx| {
24058            workspace.open_path(
24059                (worktree_id, rel_path("main.rs")),
24060                Some(pane.downgrade()),
24061                true,
24062                window,
24063                cx,
24064            )
24065        })
24066        .unwrap()
24067        .await
24068        .downcast::<Editor>()
24069        .unwrap();
24070    pane.update(cx, |pane, cx| {
24071        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24072        open_editor.update(cx, |editor, cx| {
24073            assert_eq!(
24074                editor.display_text(cx),
24075                main_text,
24076                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
24077            );
24078        })
24079    });
24080}
24081
24082#[gpui::test]
24083async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
24084    struct EmptyModalView {
24085        focus_handle: gpui::FocusHandle,
24086    }
24087    impl EventEmitter<DismissEvent> for EmptyModalView {}
24088    impl Render for EmptyModalView {
24089        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
24090            div()
24091        }
24092    }
24093    impl Focusable for EmptyModalView {
24094        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
24095            self.focus_handle.clone()
24096        }
24097    }
24098    impl workspace::ModalView for EmptyModalView {}
24099    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
24100        EmptyModalView {
24101            focus_handle: cx.focus_handle(),
24102        }
24103    }
24104
24105    init_test(cx, |_| {});
24106
24107    let fs = FakeFs::new(cx.executor());
24108    let project = Project::test(fs, [], cx).await;
24109    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24110    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
24111    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24112    let editor = cx.new_window_entity(|window, cx| {
24113        Editor::new(
24114            EditorMode::full(),
24115            buffer,
24116            Some(project.clone()),
24117            window,
24118            cx,
24119        )
24120    });
24121    workspace
24122        .update(cx, |workspace, window, cx| {
24123            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
24124        })
24125        .unwrap();
24126    editor.update_in(cx, |editor, window, cx| {
24127        editor.open_context_menu(&OpenContextMenu, window, cx);
24128        assert!(editor.mouse_context_menu.is_some());
24129    });
24130    workspace
24131        .update(cx, |workspace, window, cx| {
24132            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
24133        })
24134        .unwrap();
24135    cx.read(|cx| {
24136        assert!(editor.read(cx).mouse_context_menu.is_none());
24137    });
24138}
24139
24140fn set_linked_edit_ranges(
24141    opening: (Point, Point),
24142    closing: (Point, Point),
24143    editor: &mut Editor,
24144    cx: &mut Context<Editor>,
24145) {
24146    let Some((buffer, _)) = editor
24147        .buffer
24148        .read(cx)
24149        .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
24150    else {
24151        panic!("Failed to get buffer for selection position");
24152    };
24153    let buffer = buffer.read(cx);
24154    let buffer_id = buffer.remote_id();
24155    let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
24156    let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
24157    let mut linked_ranges = HashMap::default();
24158    linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
24159    editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
24160}
24161
24162#[gpui::test]
24163async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
24164    init_test(cx, |_| {});
24165
24166    let fs = FakeFs::new(cx.executor());
24167    fs.insert_file(path!("/file.html"), Default::default())
24168        .await;
24169
24170    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
24171
24172    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24173    let html_language = Arc::new(Language::new(
24174        LanguageConfig {
24175            name: "HTML".into(),
24176            matcher: LanguageMatcher {
24177                path_suffixes: vec!["html".to_string()],
24178                ..LanguageMatcher::default()
24179            },
24180            brackets: BracketPairConfig {
24181                pairs: vec![BracketPair {
24182                    start: "<".into(),
24183                    end: ">".into(),
24184                    close: true,
24185                    ..Default::default()
24186                }],
24187                ..Default::default()
24188            },
24189            ..Default::default()
24190        },
24191        Some(tree_sitter_html::LANGUAGE.into()),
24192    ));
24193    language_registry.add(html_language);
24194    let mut fake_servers = language_registry.register_fake_lsp(
24195        "HTML",
24196        FakeLspAdapter {
24197            capabilities: lsp::ServerCapabilities {
24198                completion_provider: Some(lsp::CompletionOptions {
24199                    resolve_provider: Some(true),
24200                    ..Default::default()
24201                }),
24202                ..Default::default()
24203            },
24204            ..Default::default()
24205        },
24206    );
24207
24208    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24209    let cx = &mut VisualTestContext::from_window(*workspace, cx);
24210
24211    let worktree_id = workspace
24212        .update(cx, |workspace, _window, cx| {
24213            workspace.project().update(cx, |project, cx| {
24214                project.worktrees(cx).next().unwrap().read(cx).id()
24215            })
24216        })
24217        .unwrap();
24218    project
24219        .update(cx, |project, cx| {
24220            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
24221        })
24222        .await
24223        .unwrap();
24224    let editor = workspace
24225        .update(cx, |workspace, window, cx| {
24226            workspace.open_path((worktree_id, rel_path("file.html")), None, true, window, cx)
24227        })
24228        .unwrap()
24229        .await
24230        .unwrap()
24231        .downcast::<Editor>()
24232        .unwrap();
24233
24234    let fake_server = fake_servers.next().await.unwrap();
24235    editor.update_in(cx, |editor, window, cx| {
24236        editor.set_text("<ad></ad>", window, cx);
24237        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24238            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
24239        });
24240        set_linked_edit_ranges(
24241            (Point::new(0, 1), Point::new(0, 3)),
24242            (Point::new(0, 6), Point::new(0, 8)),
24243            editor,
24244            cx,
24245        );
24246    });
24247    let mut completion_handle =
24248        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
24249            Ok(Some(lsp::CompletionResponse::Array(vec![
24250                lsp::CompletionItem {
24251                    label: "head".to_string(),
24252                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
24253                        lsp::InsertReplaceEdit {
24254                            new_text: "head".to_string(),
24255                            insert: lsp::Range::new(
24256                                lsp::Position::new(0, 1),
24257                                lsp::Position::new(0, 3),
24258                            ),
24259                            replace: lsp::Range::new(
24260                                lsp::Position::new(0, 1),
24261                                lsp::Position::new(0, 3),
24262                            ),
24263                        },
24264                    )),
24265                    ..Default::default()
24266                },
24267            ])))
24268        });
24269    editor.update_in(cx, |editor, window, cx| {
24270        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
24271    });
24272    cx.run_until_parked();
24273    completion_handle.next().await.unwrap();
24274    editor.update(cx, |editor, _| {
24275        assert!(
24276            editor.context_menu_visible(),
24277            "Completion menu should be visible"
24278        );
24279    });
24280    editor.update_in(cx, |editor, window, cx| {
24281        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
24282    });
24283    cx.executor().run_until_parked();
24284    editor.update(cx, |editor, cx| {
24285        assert_eq!(editor.text(cx), "<head></head>");
24286    });
24287}
24288
24289#[gpui::test]
24290async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
24291    init_test(cx, |_| {});
24292
24293    let mut cx = EditorTestContext::new(cx).await;
24294    let language = Arc::new(Language::new(
24295        LanguageConfig {
24296            name: "TSX".into(),
24297            matcher: LanguageMatcher {
24298                path_suffixes: vec!["tsx".to_string()],
24299                ..LanguageMatcher::default()
24300            },
24301            brackets: BracketPairConfig {
24302                pairs: vec![BracketPair {
24303                    start: "<".into(),
24304                    end: ">".into(),
24305                    close: true,
24306                    ..Default::default()
24307                }],
24308                ..Default::default()
24309            },
24310            linked_edit_characters: HashSet::from_iter(['.']),
24311            ..Default::default()
24312        },
24313        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
24314    ));
24315    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24316
24317    // Test typing > does not extend linked pair
24318    cx.set_state("<divˇ<div></div>");
24319    cx.update_editor(|editor, _, cx| {
24320        set_linked_edit_ranges(
24321            (Point::new(0, 1), Point::new(0, 4)),
24322            (Point::new(0, 11), Point::new(0, 14)),
24323            editor,
24324            cx,
24325        );
24326    });
24327    cx.update_editor(|editor, window, cx| {
24328        editor.handle_input(">", window, cx);
24329    });
24330    cx.assert_editor_state("<div>ˇ<div></div>");
24331
24332    // Test typing . do extend linked pair
24333    cx.set_state("<Animatedˇ></Animated>");
24334    cx.update_editor(|editor, _, cx| {
24335        set_linked_edit_ranges(
24336            (Point::new(0, 1), Point::new(0, 9)),
24337            (Point::new(0, 12), Point::new(0, 20)),
24338            editor,
24339            cx,
24340        );
24341    });
24342    cx.update_editor(|editor, window, cx| {
24343        editor.handle_input(".", window, cx);
24344    });
24345    cx.assert_editor_state("<Animated.ˇ></Animated.>");
24346    cx.update_editor(|editor, _, cx| {
24347        set_linked_edit_ranges(
24348            (Point::new(0, 1), Point::new(0, 10)),
24349            (Point::new(0, 13), Point::new(0, 21)),
24350            editor,
24351            cx,
24352        );
24353    });
24354    cx.update_editor(|editor, window, cx| {
24355        editor.handle_input("V", window, cx);
24356    });
24357    cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
24358}
24359
24360#[gpui::test]
24361async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
24362    init_test(cx, |_| {});
24363
24364    let fs = FakeFs::new(cx.executor());
24365    fs.insert_tree(
24366        path!("/root"),
24367        json!({
24368            "a": {
24369                "main.rs": "fn main() {}",
24370            },
24371            "foo": {
24372                "bar": {
24373                    "external_file.rs": "pub mod external {}",
24374                }
24375            }
24376        }),
24377    )
24378    .await;
24379
24380    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
24381    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24382    language_registry.add(rust_lang());
24383    let _fake_servers = language_registry.register_fake_lsp(
24384        "Rust",
24385        FakeLspAdapter {
24386            ..FakeLspAdapter::default()
24387        },
24388    );
24389    let (workspace, cx) =
24390        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24391    let worktree_id = workspace.update(cx, |workspace, cx| {
24392        workspace.project().update(cx, |project, cx| {
24393            project.worktrees(cx).next().unwrap().read(cx).id()
24394        })
24395    });
24396
24397    let assert_language_servers_count =
24398        |expected: usize, context: &str, cx: &mut VisualTestContext| {
24399            project.update(cx, |project, cx| {
24400                let current = project
24401                    .lsp_store()
24402                    .read(cx)
24403                    .as_local()
24404                    .unwrap()
24405                    .language_servers
24406                    .len();
24407                assert_eq!(expected, current, "{context}");
24408            });
24409        };
24410
24411    assert_language_servers_count(
24412        0,
24413        "No servers should be running before any file is open",
24414        cx,
24415    );
24416    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24417    let main_editor = workspace
24418        .update_in(cx, |workspace, window, cx| {
24419            workspace.open_path(
24420                (worktree_id, rel_path("main.rs")),
24421                Some(pane.downgrade()),
24422                true,
24423                window,
24424                cx,
24425            )
24426        })
24427        .unwrap()
24428        .await
24429        .downcast::<Editor>()
24430        .unwrap();
24431    pane.update(cx, |pane, cx| {
24432        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24433        open_editor.update(cx, |editor, cx| {
24434            assert_eq!(
24435                editor.display_text(cx),
24436                "fn main() {}",
24437                "Original main.rs text on initial open",
24438            );
24439        });
24440        assert_eq!(open_editor, main_editor);
24441    });
24442    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
24443
24444    let external_editor = workspace
24445        .update_in(cx, |workspace, window, cx| {
24446            workspace.open_abs_path(
24447                PathBuf::from("/root/foo/bar/external_file.rs"),
24448                OpenOptions::default(),
24449                window,
24450                cx,
24451            )
24452        })
24453        .await
24454        .expect("opening external file")
24455        .downcast::<Editor>()
24456        .expect("downcasted external file's open element to editor");
24457    pane.update(cx, |pane, cx| {
24458        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24459        open_editor.update(cx, |editor, cx| {
24460            assert_eq!(
24461                editor.display_text(cx),
24462                "pub mod external {}",
24463                "External file is open now",
24464            );
24465        });
24466        assert_eq!(open_editor, external_editor);
24467    });
24468    assert_language_servers_count(
24469        1,
24470        "Second, external, *.rs file should join the existing server",
24471        cx,
24472    );
24473
24474    pane.update_in(cx, |pane, window, cx| {
24475        pane.close_active_item(&CloseActiveItem::default(), window, cx)
24476    })
24477    .await
24478    .unwrap();
24479    pane.update_in(cx, |pane, window, cx| {
24480        pane.navigate_backward(&Default::default(), window, cx);
24481    });
24482    cx.run_until_parked();
24483    pane.update(cx, |pane, cx| {
24484        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24485        open_editor.update(cx, |editor, cx| {
24486            assert_eq!(
24487                editor.display_text(cx),
24488                "pub mod external {}",
24489                "External file is open now",
24490            );
24491        });
24492    });
24493    assert_language_servers_count(
24494        1,
24495        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
24496        cx,
24497    );
24498
24499    cx.update(|_, cx| {
24500        workspace::reload(cx);
24501    });
24502    assert_language_servers_count(
24503        1,
24504        "After reloading the worktree with local and external files opened, only one project should be started",
24505        cx,
24506    );
24507}
24508
24509#[gpui::test]
24510async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
24511    init_test(cx, |_| {});
24512
24513    let mut cx = EditorTestContext::new(cx).await;
24514    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24515    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24516
24517    // test cursor move to start of each line on tab
24518    // for `if`, `elif`, `else`, `while`, `with` and `for`
24519    cx.set_state(indoc! {"
24520        def main():
24521        ˇ    for item in items:
24522        ˇ        while item.active:
24523        ˇ            if item.value > 10:
24524        ˇ                continue
24525        ˇ            elif item.value < 0:
24526        ˇ                break
24527        ˇ            else:
24528        ˇ                with item.context() as ctx:
24529        ˇ                    yield count
24530        ˇ        else:
24531        ˇ            log('while else')
24532        ˇ    else:
24533        ˇ        log('for else')
24534    "});
24535    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24536    cx.assert_editor_state(indoc! {"
24537        def main():
24538            ˇfor item in items:
24539                ˇwhile item.active:
24540                    ˇif item.value > 10:
24541                        ˇcontinue
24542                    ˇelif item.value < 0:
24543                        ˇbreak
24544                    ˇelse:
24545                        ˇwith item.context() as ctx:
24546                            ˇyield count
24547                ˇelse:
24548                    ˇlog('while else')
24549            ˇelse:
24550                ˇlog('for else')
24551    "});
24552    // test relative indent is preserved when tab
24553    // for `if`, `elif`, `else`, `while`, `with` and `for`
24554    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24555    cx.assert_editor_state(indoc! {"
24556        def main():
24557                ˇfor item in items:
24558                    ˇwhile item.active:
24559                        ˇif item.value > 10:
24560                            ˇcontinue
24561                        ˇelif item.value < 0:
24562                            ˇbreak
24563                        ˇelse:
24564                            ˇwith item.context() as ctx:
24565                                ˇyield count
24566                    ˇelse:
24567                        ˇlog('while else')
24568                ˇelse:
24569                    ˇlog('for else')
24570    "});
24571
24572    // test cursor move to start of each line on tab
24573    // for `try`, `except`, `else`, `finally`, `match` and `def`
24574    cx.set_state(indoc! {"
24575        def main():
24576        ˇ    try:
24577        ˇ        fetch()
24578        ˇ    except ValueError:
24579        ˇ        handle_error()
24580        ˇ    else:
24581        ˇ        match value:
24582        ˇ            case _:
24583        ˇ    finally:
24584        ˇ        def status():
24585        ˇ            return 0
24586    "});
24587    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24588    cx.assert_editor_state(indoc! {"
24589        def main():
24590            ˇtry:
24591                ˇfetch()
24592            ˇexcept ValueError:
24593                ˇhandle_error()
24594            ˇelse:
24595                ˇmatch value:
24596                    ˇcase _:
24597            ˇfinally:
24598                ˇdef status():
24599                    ˇreturn 0
24600    "});
24601    // test relative indent is preserved when tab
24602    // for `try`, `except`, `else`, `finally`, `match` and `def`
24603    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24604    cx.assert_editor_state(indoc! {"
24605        def main():
24606                ˇtry:
24607                    ˇfetch()
24608                ˇexcept ValueError:
24609                    ˇhandle_error()
24610                ˇelse:
24611                    ˇmatch value:
24612                        ˇcase _:
24613                ˇfinally:
24614                    ˇdef status():
24615                        ˇreturn 0
24616    "});
24617}
24618
24619#[gpui::test]
24620async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
24621    init_test(cx, |_| {});
24622
24623    let mut cx = EditorTestContext::new(cx).await;
24624    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24625    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24626
24627    // test `else` auto outdents when typed inside `if` block
24628    cx.set_state(indoc! {"
24629        def main():
24630            if i == 2:
24631                return
24632                ˇ
24633    "});
24634    cx.update_editor(|editor, window, cx| {
24635        editor.handle_input("else:", window, cx);
24636    });
24637    cx.assert_editor_state(indoc! {"
24638        def main():
24639            if i == 2:
24640                return
24641            else:ˇ
24642    "});
24643
24644    // test `except` auto outdents when typed inside `try` block
24645    cx.set_state(indoc! {"
24646        def main():
24647            try:
24648                i = 2
24649                ˇ
24650    "});
24651    cx.update_editor(|editor, window, cx| {
24652        editor.handle_input("except:", window, cx);
24653    });
24654    cx.assert_editor_state(indoc! {"
24655        def main():
24656            try:
24657                i = 2
24658            except:ˇ
24659    "});
24660
24661    // test `else` auto outdents when typed inside `except` block
24662    cx.set_state(indoc! {"
24663        def main():
24664            try:
24665                i = 2
24666            except:
24667                j = 2
24668                ˇ
24669    "});
24670    cx.update_editor(|editor, window, cx| {
24671        editor.handle_input("else:", window, cx);
24672    });
24673    cx.assert_editor_state(indoc! {"
24674        def main():
24675            try:
24676                i = 2
24677            except:
24678                j = 2
24679            else:ˇ
24680    "});
24681
24682    // test `finally` auto outdents when typed inside `else` block
24683    cx.set_state(indoc! {"
24684        def main():
24685            try:
24686                i = 2
24687            except:
24688                j = 2
24689            else:
24690                k = 2
24691                ˇ
24692    "});
24693    cx.update_editor(|editor, window, cx| {
24694        editor.handle_input("finally:", window, cx);
24695    });
24696    cx.assert_editor_state(indoc! {"
24697        def main():
24698            try:
24699                i = 2
24700            except:
24701                j = 2
24702            else:
24703                k = 2
24704            finally:ˇ
24705    "});
24706
24707    // test `else` does not outdents when typed inside `except` block right after for block
24708    cx.set_state(indoc! {"
24709        def main():
24710            try:
24711                i = 2
24712            except:
24713                for i in range(n):
24714                    pass
24715                ˇ
24716    "});
24717    cx.update_editor(|editor, window, cx| {
24718        editor.handle_input("else:", window, cx);
24719    });
24720    cx.assert_editor_state(indoc! {"
24721        def main():
24722            try:
24723                i = 2
24724            except:
24725                for i in range(n):
24726                    pass
24727                else:ˇ
24728    "});
24729
24730    // test `finally` auto outdents when typed inside `else` block right after for block
24731    cx.set_state(indoc! {"
24732        def main():
24733            try:
24734                i = 2
24735            except:
24736                j = 2
24737            else:
24738                for i in range(n):
24739                    pass
24740                ˇ
24741    "});
24742    cx.update_editor(|editor, window, cx| {
24743        editor.handle_input("finally:", window, cx);
24744    });
24745    cx.assert_editor_state(indoc! {"
24746        def main():
24747            try:
24748                i = 2
24749            except:
24750                j = 2
24751            else:
24752                for i in range(n):
24753                    pass
24754            finally:ˇ
24755    "});
24756
24757    // test `except` outdents to inner "try" block
24758    cx.set_state(indoc! {"
24759        def main():
24760            try:
24761                i = 2
24762                if i == 2:
24763                    try:
24764                        i = 3
24765                        ˇ
24766    "});
24767    cx.update_editor(|editor, window, cx| {
24768        editor.handle_input("except:", window, cx);
24769    });
24770    cx.assert_editor_state(indoc! {"
24771        def main():
24772            try:
24773                i = 2
24774                if i == 2:
24775                    try:
24776                        i = 3
24777                    except:ˇ
24778    "});
24779
24780    // test `except` outdents to outer "try" block
24781    cx.set_state(indoc! {"
24782        def main():
24783            try:
24784                i = 2
24785                if i == 2:
24786                    try:
24787                        i = 3
24788                ˇ
24789    "});
24790    cx.update_editor(|editor, window, cx| {
24791        editor.handle_input("except:", window, cx);
24792    });
24793    cx.assert_editor_state(indoc! {"
24794        def main():
24795            try:
24796                i = 2
24797                if i == 2:
24798                    try:
24799                        i = 3
24800            except:ˇ
24801    "});
24802
24803    // test `else` stays at correct indent when typed after `for` block
24804    cx.set_state(indoc! {"
24805        def main():
24806            for i in range(10):
24807                if i == 3:
24808                    break
24809            ˇ
24810    "});
24811    cx.update_editor(|editor, window, cx| {
24812        editor.handle_input("else:", window, cx);
24813    });
24814    cx.assert_editor_state(indoc! {"
24815        def main():
24816            for i in range(10):
24817                if i == 3:
24818                    break
24819            else:ˇ
24820    "});
24821
24822    // test does not outdent on typing after line with square brackets
24823    cx.set_state(indoc! {"
24824        def f() -> list[str]:
24825            ˇ
24826    "});
24827    cx.update_editor(|editor, window, cx| {
24828        editor.handle_input("a", window, cx);
24829    });
24830    cx.assert_editor_state(indoc! {"
24831        def f() -> list[str]:
2483224833    "});
24834
24835    // test does not outdent on typing : after case keyword
24836    cx.set_state(indoc! {"
24837        match 1:
24838            caseˇ
24839    "});
24840    cx.update_editor(|editor, window, cx| {
24841        editor.handle_input(":", window, cx);
24842    });
24843    cx.assert_editor_state(indoc! {"
24844        match 1:
24845            case:ˇ
24846    "});
24847}
24848
24849#[gpui::test]
24850async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
24851    init_test(cx, |_| {});
24852    update_test_language_settings(cx, |settings| {
24853        settings.defaults.extend_comment_on_newline = Some(false);
24854    });
24855    let mut cx = EditorTestContext::new(cx).await;
24856    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24857    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24858
24859    // test correct indent after newline on comment
24860    cx.set_state(indoc! {"
24861        # COMMENT:ˇ
24862    "});
24863    cx.update_editor(|editor, window, cx| {
24864        editor.newline(&Newline, window, cx);
24865    });
24866    cx.assert_editor_state(indoc! {"
24867        # COMMENT:
24868        ˇ
24869    "});
24870
24871    // test correct indent after newline in brackets
24872    cx.set_state(indoc! {"
24873        {ˇ}
24874    "});
24875    cx.update_editor(|editor, window, cx| {
24876        editor.newline(&Newline, window, cx);
24877    });
24878    cx.run_until_parked();
24879    cx.assert_editor_state(indoc! {"
24880        {
24881            ˇ
24882        }
24883    "});
24884
24885    cx.set_state(indoc! {"
24886        (ˇ)
24887    "});
24888    cx.update_editor(|editor, window, cx| {
24889        editor.newline(&Newline, window, cx);
24890    });
24891    cx.run_until_parked();
24892    cx.assert_editor_state(indoc! {"
24893        (
24894            ˇ
24895        )
24896    "});
24897
24898    // do not indent after empty lists or dictionaries
24899    cx.set_state(indoc! {"
24900        a = []ˇ
24901    "});
24902    cx.update_editor(|editor, window, cx| {
24903        editor.newline(&Newline, window, cx);
24904    });
24905    cx.run_until_parked();
24906    cx.assert_editor_state(indoc! {"
24907        a = []
24908        ˇ
24909    "});
24910}
24911
24912#[gpui::test]
24913async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
24914    init_test(cx, |_| {});
24915
24916    let mut cx = EditorTestContext::new(cx).await;
24917    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24918    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24919
24920    // test cursor move to start of each line on tab
24921    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
24922    cx.set_state(indoc! {"
24923        function main() {
24924        ˇ    for item in $items; do
24925        ˇ        while [ -n \"$item\" ]; do
24926        ˇ            if [ \"$value\" -gt 10 ]; then
24927        ˇ                continue
24928        ˇ            elif [ \"$value\" -lt 0 ]; then
24929        ˇ                break
24930        ˇ            else
24931        ˇ                echo \"$item\"
24932        ˇ            fi
24933        ˇ        done
24934        ˇ    done
24935        ˇ}
24936    "});
24937    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24938    cx.assert_editor_state(indoc! {"
24939        function main() {
24940            ˇfor item in $items; do
24941                ˇwhile [ -n \"$item\" ]; do
24942                    ˇif [ \"$value\" -gt 10 ]; then
24943                        ˇcontinue
24944                    ˇelif [ \"$value\" -lt 0 ]; then
24945                        ˇbreak
24946                    ˇelse
24947                        ˇecho \"$item\"
24948                    ˇfi
24949                ˇdone
24950            ˇdone
24951        ˇ}
24952    "});
24953    // test relative indent is preserved when tab
24954    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24955    cx.assert_editor_state(indoc! {"
24956        function main() {
24957                ˇfor item in $items; do
24958                    ˇwhile [ -n \"$item\" ]; do
24959                        ˇif [ \"$value\" -gt 10 ]; then
24960                            ˇcontinue
24961                        ˇelif [ \"$value\" -lt 0 ]; then
24962                            ˇbreak
24963                        ˇelse
24964                            ˇecho \"$item\"
24965                        ˇfi
24966                    ˇdone
24967                ˇdone
24968            ˇ}
24969    "});
24970
24971    // test cursor move to start of each line on tab
24972    // for `case` statement with patterns
24973    cx.set_state(indoc! {"
24974        function handle() {
24975        ˇ    case \"$1\" in
24976        ˇ        start)
24977        ˇ            echo \"a\"
24978        ˇ            ;;
24979        ˇ        stop)
24980        ˇ            echo \"b\"
24981        ˇ            ;;
24982        ˇ        *)
24983        ˇ            echo \"c\"
24984        ˇ            ;;
24985        ˇ    esac
24986        ˇ}
24987    "});
24988    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24989    cx.assert_editor_state(indoc! {"
24990        function handle() {
24991            ˇcase \"$1\" in
24992                ˇstart)
24993                    ˇecho \"a\"
24994                    ˇ;;
24995                ˇstop)
24996                    ˇecho \"b\"
24997                    ˇ;;
24998                ˇ*)
24999                    ˇecho \"c\"
25000                    ˇ;;
25001            ˇesac
25002        ˇ}
25003    "});
25004}
25005
25006#[gpui::test]
25007async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
25008    init_test(cx, |_| {});
25009
25010    let mut cx = EditorTestContext::new(cx).await;
25011    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25012    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25013
25014    // test indents on comment insert
25015    cx.set_state(indoc! {"
25016        function main() {
25017        ˇ    for item in $items; do
25018        ˇ        while [ -n \"$item\" ]; do
25019        ˇ            if [ \"$value\" -gt 10 ]; then
25020        ˇ                continue
25021        ˇ            elif [ \"$value\" -lt 0 ]; then
25022        ˇ                break
25023        ˇ            else
25024        ˇ                echo \"$item\"
25025        ˇ            fi
25026        ˇ        done
25027        ˇ    done
25028        ˇ}
25029    "});
25030    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
25031    cx.assert_editor_state(indoc! {"
25032        function main() {
25033        #ˇ    for item in $items; do
25034        #ˇ        while [ -n \"$item\" ]; do
25035        #ˇ            if [ \"$value\" -gt 10 ]; then
25036        #ˇ                continue
25037        #ˇ            elif [ \"$value\" -lt 0 ]; then
25038        #ˇ                break
25039        #ˇ            else
25040        #ˇ                echo \"$item\"
25041        #ˇ            fi
25042        #ˇ        done
25043        #ˇ    done
25044        #ˇ}
25045    "});
25046}
25047
25048#[gpui::test]
25049async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
25050    init_test(cx, |_| {});
25051
25052    let mut cx = EditorTestContext::new(cx).await;
25053    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25054    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25055
25056    // test `else` auto outdents when typed inside `if` block
25057    cx.set_state(indoc! {"
25058        if [ \"$1\" = \"test\" ]; then
25059            echo \"foo bar\"
25060            ˇ
25061    "});
25062    cx.update_editor(|editor, window, cx| {
25063        editor.handle_input("else", window, cx);
25064    });
25065    cx.assert_editor_state(indoc! {"
25066        if [ \"$1\" = \"test\" ]; then
25067            echo \"foo bar\"
25068        elseˇ
25069    "});
25070
25071    // test `elif` auto outdents when typed inside `if` block
25072    cx.set_state(indoc! {"
25073        if [ \"$1\" = \"test\" ]; then
25074            echo \"foo bar\"
25075            ˇ
25076    "});
25077    cx.update_editor(|editor, window, cx| {
25078        editor.handle_input("elif", window, cx);
25079    });
25080    cx.assert_editor_state(indoc! {"
25081        if [ \"$1\" = \"test\" ]; then
25082            echo \"foo bar\"
25083        elifˇ
25084    "});
25085
25086    // test `fi` auto outdents when typed inside `else` block
25087    cx.set_state(indoc! {"
25088        if [ \"$1\" = \"test\" ]; then
25089            echo \"foo bar\"
25090        else
25091            echo \"bar baz\"
25092            ˇ
25093    "});
25094    cx.update_editor(|editor, window, cx| {
25095        editor.handle_input("fi", window, cx);
25096    });
25097    cx.assert_editor_state(indoc! {"
25098        if [ \"$1\" = \"test\" ]; then
25099            echo \"foo bar\"
25100        else
25101            echo \"bar baz\"
25102        fiˇ
25103    "});
25104
25105    // test `done` auto outdents when typed inside `while` block
25106    cx.set_state(indoc! {"
25107        while read line; do
25108            echo \"$line\"
25109            ˇ
25110    "});
25111    cx.update_editor(|editor, window, cx| {
25112        editor.handle_input("done", window, cx);
25113    });
25114    cx.assert_editor_state(indoc! {"
25115        while read line; do
25116            echo \"$line\"
25117        doneˇ
25118    "});
25119
25120    // test `done` auto outdents when typed inside `for` block
25121    cx.set_state(indoc! {"
25122        for file in *.txt; do
25123            cat \"$file\"
25124            ˇ
25125    "});
25126    cx.update_editor(|editor, window, cx| {
25127        editor.handle_input("done", window, cx);
25128    });
25129    cx.assert_editor_state(indoc! {"
25130        for file in *.txt; do
25131            cat \"$file\"
25132        doneˇ
25133    "});
25134
25135    // test `esac` auto outdents when typed inside `case` block
25136    cx.set_state(indoc! {"
25137        case \"$1\" in
25138            start)
25139                echo \"foo bar\"
25140                ;;
25141            stop)
25142                echo \"bar baz\"
25143                ;;
25144            ˇ
25145    "});
25146    cx.update_editor(|editor, window, cx| {
25147        editor.handle_input("esac", window, cx);
25148    });
25149    cx.assert_editor_state(indoc! {"
25150        case \"$1\" in
25151            start)
25152                echo \"foo bar\"
25153                ;;
25154            stop)
25155                echo \"bar baz\"
25156                ;;
25157        esacˇ
25158    "});
25159
25160    // test `*)` auto outdents when typed inside `case` block
25161    cx.set_state(indoc! {"
25162        case \"$1\" in
25163            start)
25164                echo \"foo bar\"
25165                ;;
25166                ˇ
25167    "});
25168    cx.update_editor(|editor, window, cx| {
25169        editor.handle_input("*)", window, cx);
25170    });
25171    cx.assert_editor_state(indoc! {"
25172        case \"$1\" in
25173            start)
25174                echo \"foo bar\"
25175                ;;
25176            *)ˇ
25177    "});
25178
25179    // test `fi` outdents to correct level with nested if blocks
25180    cx.set_state(indoc! {"
25181        if [ \"$1\" = \"test\" ]; then
25182            echo \"outer if\"
25183            if [ \"$2\" = \"debug\" ]; then
25184                echo \"inner if\"
25185                ˇ
25186    "});
25187    cx.update_editor(|editor, window, cx| {
25188        editor.handle_input("fi", window, cx);
25189    });
25190    cx.assert_editor_state(indoc! {"
25191        if [ \"$1\" = \"test\" ]; then
25192            echo \"outer if\"
25193            if [ \"$2\" = \"debug\" ]; then
25194                echo \"inner if\"
25195            fiˇ
25196    "});
25197}
25198
25199#[gpui::test]
25200async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
25201    init_test(cx, |_| {});
25202    update_test_language_settings(cx, |settings| {
25203        settings.defaults.extend_comment_on_newline = Some(false);
25204    });
25205    let mut cx = EditorTestContext::new(cx).await;
25206    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25207    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25208
25209    // test correct indent after newline on comment
25210    cx.set_state(indoc! {"
25211        # COMMENT:ˇ
25212    "});
25213    cx.update_editor(|editor, window, cx| {
25214        editor.newline(&Newline, window, cx);
25215    });
25216    cx.assert_editor_state(indoc! {"
25217        # COMMENT:
25218        ˇ
25219    "});
25220
25221    // test correct indent after newline after `then`
25222    cx.set_state(indoc! {"
25223
25224        if [ \"$1\" = \"test\" ]; thenˇ
25225    "});
25226    cx.update_editor(|editor, window, cx| {
25227        editor.newline(&Newline, window, cx);
25228    });
25229    cx.run_until_parked();
25230    cx.assert_editor_state(indoc! {"
25231
25232        if [ \"$1\" = \"test\" ]; then
25233            ˇ
25234    "});
25235
25236    // test correct indent after newline after `else`
25237    cx.set_state(indoc! {"
25238        if [ \"$1\" = \"test\" ]; then
25239        elseˇ
25240    "});
25241    cx.update_editor(|editor, window, cx| {
25242        editor.newline(&Newline, window, cx);
25243    });
25244    cx.run_until_parked();
25245    cx.assert_editor_state(indoc! {"
25246        if [ \"$1\" = \"test\" ]; then
25247        else
25248            ˇ
25249    "});
25250
25251    // test correct indent after newline after `elif`
25252    cx.set_state(indoc! {"
25253        if [ \"$1\" = \"test\" ]; then
25254        elifˇ
25255    "});
25256    cx.update_editor(|editor, window, cx| {
25257        editor.newline(&Newline, window, cx);
25258    });
25259    cx.run_until_parked();
25260    cx.assert_editor_state(indoc! {"
25261        if [ \"$1\" = \"test\" ]; then
25262        elif
25263            ˇ
25264    "});
25265
25266    // test correct indent after newline after `do`
25267    cx.set_state(indoc! {"
25268        for file in *.txt; doˇ
25269    "});
25270    cx.update_editor(|editor, window, cx| {
25271        editor.newline(&Newline, window, cx);
25272    });
25273    cx.run_until_parked();
25274    cx.assert_editor_state(indoc! {"
25275        for file in *.txt; do
25276            ˇ
25277    "});
25278
25279    // test correct indent after newline after case pattern
25280    cx.set_state(indoc! {"
25281        case \"$1\" in
25282            start)ˇ
25283    "});
25284    cx.update_editor(|editor, window, cx| {
25285        editor.newline(&Newline, window, cx);
25286    });
25287    cx.run_until_parked();
25288    cx.assert_editor_state(indoc! {"
25289        case \"$1\" in
25290            start)
25291                ˇ
25292    "});
25293
25294    // test correct indent after newline after case pattern
25295    cx.set_state(indoc! {"
25296        case \"$1\" in
25297            start)
25298                ;;
25299            *)ˇ
25300    "});
25301    cx.update_editor(|editor, window, cx| {
25302        editor.newline(&Newline, window, cx);
25303    });
25304    cx.run_until_parked();
25305    cx.assert_editor_state(indoc! {"
25306        case \"$1\" in
25307            start)
25308                ;;
25309            *)
25310                ˇ
25311    "});
25312
25313    // test correct indent after newline after function opening brace
25314    cx.set_state(indoc! {"
25315        function test() {ˇ}
25316    "});
25317    cx.update_editor(|editor, window, cx| {
25318        editor.newline(&Newline, window, cx);
25319    });
25320    cx.run_until_parked();
25321    cx.assert_editor_state(indoc! {"
25322        function test() {
25323            ˇ
25324        }
25325    "});
25326
25327    // test no extra indent after semicolon on same line
25328    cx.set_state(indoc! {"
25329        echo \"test\"25330    "});
25331    cx.update_editor(|editor, window, cx| {
25332        editor.newline(&Newline, window, cx);
25333    });
25334    cx.run_until_parked();
25335    cx.assert_editor_state(indoc! {"
25336        echo \"test\";
25337        ˇ
25338    "});
25339}
25340
25341fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
25342    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
25343    point..point
25344}
25345
25346#[track_caller]
25347fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
25348    let (text, ranges) = marked_text_ranges(marked_text, true);
25349    assert_eq!(editor.text(cx), text);
25350    assert_eq!(
25351        editor.selections.ranges(&editor.display_snapshot(cx)),
25352        ranges,
25353        "Assert selections are {}",
25354        marked_text
25355    );
25356}
25357
25358pub fn handle_signature_help_request(
25359    cx: &mut EditorLspTestContext,
25360    mocked_response: lsp::SignatureHelp,
25361) -> impl Future<Output = ()> + use<> {
25362    let mut request =
25363        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
25364            let mocked_response = mocked_response.clone();
25365            async move { Ok(Some(mocked_response)) }
25366        });
25367
25368    async move {
25369        request.next().await;
25370    }
25371}
25372
25373#[track_caller]
25374pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
25375    cx.update_editor(|editor, _, _| {
25376        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
25377            let entries = menu.entries.borrow();
25378            let entries = entries
25379                .iter()
25380                .map(|entry| entry.string.as_str())
25381                .collect::<Vec<_>>();
25382            assert_eq!(entries, expected);
25383        } else {
25384            panic!("Expected completions menu");
25385        }
25386    });
25387}
25388
25389/// Handle completion request passing a marked string specifying where the completion
25390/// should be triggered from using '|' character, what range should be replaced, and what completions
25391/// should be returned using '<' and '>' to delimit the range.
25392///
25393/// Also see `handle_completion_request_with_insert_and_replace`.
25394#[track_caller]
25395pub fn handle_completion_request(
25396    marked_string: &str,
25397    completions: Vec<&'static str>,
25398    is_incomplete: bool,
25399    counter: Arc<AtomicUsize>,
25400    cx: &mut EditorLspTestContext,
25401) -> impl Future<Output = ()> {
25402    let complete_from_marker: TextRangeMarker = '|'.into();
25403    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25404    let (_, mut marked_ranges) = marked_text_ranges_by(
25405        marked_string,
25406        vec![complete_from_marker.clone(), replace_range_marker.clone()],
25407    );
25408
25409    let complete_from_position =
25410        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25411    let replace_range =
25412        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25413
25414    let mut request =
25415        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25416            let completions = completions.clone();
25417            counter.fetch_add(1, atomic::Ordering::Release);
25418            async move {
25419                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25420                assert_eq!(
25421                    params.text_document_position.position,
25422                    complete_from_position
25423                );
25424                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
25425                    is_incomplete,
25426                    item_defaults: None,
25427                    items: completions
25428                        .iter()
25429                        .map(|completion_text| lsp::CompletionItem {
25430                            label: completion_text.to_string(),
25431                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
25432                                range: replace_range,
25433                                new_text: completion_text.to_string(),
25434                            })),
25435                            ..Default::default()
25436                        })
25437                        .collect(),
25438                })))
25439            }
25440        });
25441
25442    async move {
25443        request.next().await;
25444    }
25445}
25446
25447/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
25448/// given instead, which also contains an `insert` range.
25449///
25450/// This function uses markers to define ranges:
25451/// - `|` marks the cursor position
25452/// - `<>` marks the replace range
25453/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
25454pub fn handle_completion_request_with_insert_and_replace(
25455    cx: &mut EditorLspTestContext,
25456    marked_string: &str,
25457    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
25458    counter: Arc<AtomicUsize>,
25459) -> impl Future<Output = ()> {
25460    let complete_from_marker: TextRangeMarker = '|'.into();
25461    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25462    let insert_range_marker: TextRangeMarker = ('{', '}').into();
25463
25464    let (_, mut marked_ranges) = marked_text_ranges_by(
25465        marked_string,
25466        vec![
25467            complete_from_marker.clone(),
25468            replace_range_marker.clone(),
25469            insert_range_marker.clone(),
25470        ],
25471    );
25472
25473    let complete_from_position =
25474        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25475    let replace_range =
25476        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25477
25478    let insert_range = match marked_ranges.remove(&insert_range_marker) {
25479        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
25480        _ => lsp::Range {
25481            start: replace_range.start,
25482            end: complete_from_position,
25483        },
25484    };
25485
25486    let mut request =
25487        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25488            let completions = completions.clone();
25489            counter.fetch_add(1, atomic::Ordering::Release);
25490            async move {
25491                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25492                assert_eq!(
25493                    params.text_document_position.position, complete_from_position,
25494                    "marker `|` position doesn't match",
25495                );
25496                Ok(Some(lsp::CompletionResponse::Array(
25497                    completions
25498                        .iter()
25499                        .map(|(label, new_text)| lsp::CompletionItem {
25500                            label: label.to_string(),
25501                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
25502                                lsp::InsertReplaceEdit {
25503                                    insert: insert_range,
25504                                    replace: replace_range,
25505                                    new_text: new_text.to_string(),
25506                                },
25507                            )),
25508                            ..Default::default()
25509                        })
25510                        .collect(),
25511                )))
25512            }
25513        });
25514
25515    async move {
25516        request.next().await;
25517    }
25518}
25519
25520fn handle_resolve_completion_request(
25521    cx: &mut EditorLspTestContext,
25522    edits: Option<Vec<(&'static str, &'static str)>>,
25523) -> impl Future<Output = ()> {
25524    let edits = edits.map(|edits| {
25525        edits
25526            .iter()
25527            .map(|(marked_string, new_text)| {
25528                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
25529                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
25530                lsp::TextEdit::new(replace_range, new_text.to_string())
25531            })
25532            .collect::<Vec<_>>()
25533    });
25534
25535    let mut request =
25536        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
25537            let edits = edits.clone();
25538            async move {
25539                Ok(lsp::CompletionItem {
25540                    additional_text_edits: edits,
25541                    ..Default::default()
25542                })
25543            }
25544        });
25545
25546    async move {
25547        request.next().await;
25548    }
25549}
25550
25551pub(crate) fn update_test_language_settings(
25552    cx: &mut TestAppContext,
25553    f: impl Fn(&mut AllLanguageSettingsContent),
25554) {
25555    cx.update(|cx| {
25556        SettingsStore::update_global(cx, |store, cx| {
25557            store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
25558        });
25559    });
25560}
25561
25562pub(crate) fn update_test_project_settings(
25563    cx: &mut TestAppContext,
25564    f: impl Fn(&mut ProjectSettingsContent),
25565) {
25566    cx.update(|cx| {
25567        SettingsStore::update_global(cx, |store, cx| {
25568            store.update_user_settings(cx, |settings| f(&mut settings.project));
25569        });
25570    });
25571}
25572
25573pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
25574    cx.update(|cx| {
25575        assets::Assets.load_test_fonts(cx);
25576        let store = SettingsStore::test(cx);
25577        cx.set_global(store);
25578        theme::init(theme::LoadThemes::JustBase, cx);
25579        release_channel::init(SemanticVersion::default(), cx);
25580        client::init_settings(cx);
25581        language::init(cx);
25582        Project::init_settings(cx);
25583        workspace::init_settings(cx);
25584        crate::init(cx);
25585    });
25586    zlog::init_test();
25587    update_test_language_settings(cx, f);
25588}
25589
25590#[track_caller]
25591fn assert_hunk_revert(
25592    not_reverted_text_with_selections: &str,
25593    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
25594    expected_reverted_text_with_selections: &str,
25595    base_text: &str,
25596    cx: &mut EditorLspTestContext,
25597) {
25598    cx.set_state(not_reverted_text_with_selections);
25599    cx.set_head_text(base_text);
25600    cx.executor().run_until_parked();
25601
25602    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
25603        let snapshot = editor.snapshot(window, cx);
25604        let reverted_hunk_statuses = snapshot
25605            .buffer_snapshot()
25606            .diff_hunks_in_range(0..snapshot.buffer_snapshot().len())
25607            .map(|hunk| hunk.status().kind)
25608            .collect::<Vec<_>>();
25609
25610        editor.git_restore(&Default::default(), window, cx);
25611        reverted_hunk_statuses
25612    });
25613    cx.executor().run_until_parked();
25614    cx.assert_editor_state(expected_reverted_text_with_selections);
25615    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
25616}
25617
25618#[gpui::test(iterations = 10)]
25619async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
25620    init_test(cx, |_| {});
25621
25622    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
25623    let counter = diagnostic_requests.clone();
25624
25625    let fs = FakeFs::new(cx.executor());
25626    fs.insert_tree(
25627        path!("/a"),
25628        json!({
25629            "first.rs": "fn main() { let a = 5; }",
25630            "second.rs": "// Test file",
25631        }),
25632    )
25633    .await;
25634
25635    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25636    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25637    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25638
25639    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25640    language_registry.add(rust_lang());
25641    let mut fake_servers = language_registry.register_fake_lsp(
25642        "Rust",
25643        FakeLspAdapter {
25644            capabilities: lsp::ServerCapabilities {
25645                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
25646                    lsp::DiagnosticOptions {
25647                        identifier: None,
25648                        inter_file_dependencies: true,
25649                        workspace_diagnostics: true,
25650                        work_done_progress_options: Default::default(),
25651                    },
25652                )),
25653                ..Default::default()
25654            },
25655            ..Default::default()
25656        },
25657    );
25658
25659    let editor = workspace
25660        .update(cx, |workspace, window, cx| {
25661            workspace.open_abs_path(
25662                PathBuf::from(path!("/a/first.rs")),
25663                OpenOptions::default(),
25664                window,
25665                cx,
25666            )
25667        })
25668        .unwrap()
25669        .await
25670        .unwrap()
25671        .downcast::<Editor>()
25672        .unwrap();
25673    let fake_server = fake_servers.next().await.unwrap();
25674    let server_id = fake_server.server.server_id();
25675    let mut first_request = fake_server
25676        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
25677            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
25678            let result_id = Some(new_result_id.to_string());
25679            assert_eq!(
25680                params.text_document.uri,
25681                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25682            );
25683            async move {
25684                Ok(lsp::DocumentDiagnosticReportResult::Report(
25685                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
25686                        related_documents: None,
25687                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
25688                            items: Vec::new(),
25689                            result_id,
25690                        },
25691                    }),
25692                ))
25693            }
25694        });
25695
25696    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
25697        project.update(cx, |project, cx| {
25698            let buffer_id = editor
25699                .read(cx)
25700                .buffer()
25701                .read(cx)
25702                .as_singleton()
25703                .expect("created a singleton buffer")
25704                .read(cx)
25705                .remote_id();
25706            let buffer_result_id = project
25707                .lsp_store()
25708                .read(cx)
25709                .result_id(server_id, buffer_id, cx);
25710            assert_eq!(expected, buffer_result_id);
25711        });
25712    };
25713
25714    ensure_result_id(None, cx);
25715    cx.executor().advance_clock(Duration::from_millis(60));
25716    cx.executor().run_until_parked();
25717    assert_eq!(
25718        diagnostic_requests.load(atomic::Ordering::Acquire),
25719        1,
25720        "Opening file should trigger diagnostic request"
25721    );
25722    first_request
25723        .next()
25724        .await
25725        .expect("should have sent the first diagnostics pull request");
25726    ensure_result_id(Some("1".to_string()), cx);
25727
25728    // Editing should trigger diagnostics
25729    editor.update_in(cx, |editor, window, cx| {
25730        editor.handle_input("2", window, cx)
25731    });
25732    cx.executor().advance_clock(Duration::from_millis(60));
25733    cx.executor().run_until_parked();
25734    assert_eq!(
25735        diagnostic_requests.load(atomic::Ordering::Acquire),
25736        2,
25737        "Editing should trigger diagnostic request"
25738    );
25739    ensure_result_id(Some("2".to_string()), cx);
25740
25741    // Moving cursor should not trigger diagnostic request
25742    editor.update_in(cx, |editor, window, cx| {
25743        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25744            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
25745        });
25746    });
25747    cx.executor().advance_clock(Duration::from_millis(60));
25748    cx.executor().run_until_parked();
25749    assert_eq!(
25750        diagnostic_requests.load(atomic::Ordering::Acquire),
25751        2,
25752        "Cursor movement should not trigger diagnostic request"
25753    );
25754    ensure_result_id(Some("2".to_string()), cx);
25755    // Multiple rapid edits should be debounced
25756    for _ in 0..5 {
25757        editor.update_in(cx, |editor, window, cx| {
25758            editor.handle_input("x", window, cx)
25759        });
25760    }
25761    cx.executor().advance_clock(Duration::from_millis(60));
25762    cx.executor().run_until_parked();
25763
25764    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
25765    assert!(
25766        final_requests <= 4,
25767        "Multiple rapid edits should be debounced (got {final_requests} requests)",
25768    );
25769    ensure_result_id(Some(final_requests.to_string()), cx);
25770}
25771
25772#[gpui::test]
25773async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
25774    // Regression test for issue #11671
25775    // Previously, adding a cursor after moving multiple cursors would reset
25776    // the cursor count instead of adding to the existing cursors.
25777    init_test(cx, |_| {});
25778    let mut cx = EditorTestContext::new(cx).await;
25779
25780    // Create a simple buffer with cursor at start
25781    cx.set_state(indoc! {"
25782        ˇaaaa
25783        bbbb
25784        cccc
25785        dddd
25786        eeee
25787        ffff
25788        gggg
25789        hhhh"});
25790
25791    // Add 2 cursors below (so we have 3 total)
25792    cx.update_editor(|editor, window, cx| {
25793        editor.add_selection_below(&Default::default(), window, cx);
25794        editor.add_selection_below(&Default::default(), window, cx);
25795    });
25796
25797    // Verify we have 3 cursors
25798    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
25799    assert_eq!(
25800        initial_count, 3,
25801        "Should have 3 cursors after adding 2 below"
25802    );
25803
25804    // Move down one line
25805    cx.update_editor(|editor, window, cx| {
25806        editor.move_down(&MoveDown, window, cx);
25807    });
25808
25809    // Add another cursor below
25810    cx.update_editor(|editor, window, cx| {
25811        editor.add_selection_below(&Default::default(), window, cx);
25812    });
25813
25814    // Should now have 4 cursors (3 original + 1 new)
25815    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
25816    assert_eq!(
25817        final_count, 4,
25818        "Should have 4 cursors after moving and adding another"
25819    );
25820}
25821
25822#[gpui::test]
25823async fn test_add_selection_skip_soft_wrap_option(cx: &mut TestAppContext) {
25824    init_test(cx, |_| {});
25825
25826    let mut cx = EditorTestContext::new(cx).await;
25827
25828    cx.set_state(indoc!(
25829        r#"ˇThis is a very long line that will be wrapped when soft wrapping is enabled
25830           Second line here"#
25831    ));
25832
25833    cx.update_editor(|editor, window, cx| {
25834        // Enable soft wrapping with a narrow width to force soft wrapping and
25835        // confirm that more than 2 rows are being displayed.
25836        editor.set_wrap_width(Some(100.0.into()), cx);
25837        assert!(editor.display_text(cx).lines().count() > 2);
25838
25839        editor.add_selection_below(
25840            &AddSelectionBelow {
25841                skip_soft_wrap: true,
25842            },
25843            window,
25844            cx,
25845        );
25846
25847        assert_eq!(
25848            display_ranges(editor, cx),
25849            &[
25850                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
25851                DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(8), 0),
25852            ]
25853        );
25854
25855        editor.add_selection_above(
25856            &AddSelectionAbove {
25857                skip_soft_wrap: true,
25858            },
25859            window,
25860            cx,
25861        );
25862
25863        assert_eq!(
25864            display_ranges(editor, cx),
25865            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
25866        );
25867
25868        editor.add_selection_below(
25869            &AddSelectionBelow {
25870                skip_soft_wrap: false,
25871            },
25872            window,
25873            cx,
25874        );
25875
25876        assert_eq!(
25877            display_ranges(editor, cx),
25878            &[
25879                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
25880                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
25881            ]
25882        );
25883
25884        editor.add_selection_above(
25885            &AddSelectionAbove {
25886                skip_soft_wrap: false,
25887            },
25888            window,
25889            cx,
25890        );
25891
25892        assert_eq!(
25893            display_ranges(editor, cx),
25894            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
25895        );
25896    });
25897}
25898
25899#[gpui::test(iterations = 10)]
25900async fn test_document_colors(cx: &mut TestAppContext) {
25901    let expected_color = Rgba {
25902        r: 0.33,
25903        g: 0.33,
25904        b: 0.33,
25905        a: 0.33,
25906    };
25907
25908    init_test(cx, |_| {});
25909
25910    let fs = FakeFs::new(cx.executor());
25911    fs.insert_tree(
25912        path!("/a"),
25913        json!({
25914            "first.rs": "fn main() { let a = 5; }",
25915        }),
25916    )
25917    .await;
25918
25919    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25920    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25921    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25922
25923    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25924    language_registry.add(rust_lang());
25925    let mut fake_servers = language_registry.register_fake_lsp(
25926        "Rust",
25927        FakeLspAdapter {
25928            capabilities: lsp::ServerCapabilities {
25929                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
25930                ..lsp::ServerCapabilities::default()
25931            },
25932            name: "rust-analyzer",
25933            ..FakeLspAdapter::default()
25934        },
25935    );
25936    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
25937        "Rust",
25938        FakeLspAdapter {
25939            capabilities: lsp::ServerCapabilities {
25940                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
25941                ..lsp::ServerCapabilities::default()
25942            },
25943            name: "not-rust-analyzer",
25944            ..FakeLspAdapter::default()
25945        },
25946    );
25947
25948    let editor = workspace
25949        .update(cx, |workspace, window, cx| {
25950            workspace.open_abs_path(
25951                PathBuf::from(path!("/a/first.rs")),
25952                OpenOptions::default(),
25953                window,
25954                cx,
25955            )
25956        })
25957        .unwrap()
25958        .await
25959        .unwrap()
25960        .downcast::<Editor>()
25961        .unwrap();
25962    let fake_language_server = fake_servers.next().await.unwrap();
25963    let fake_language_server_without_capabilities =
25964        fake_servers_without_capabilities.next().await.unwrap();
25965    let requests_made = Arc::new(AtomicUsize::new(0));
25966    let closure_requests_made = Arc::clone(&requests_made);
25967    let mut color_request_handle = fake_language_server
25968        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
25969            let requests_made = Arc::clone(&closure_requests_made);
25970            async move {
25971                assert_eq!(
25972                    params.text_document.uri,
25973                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25974                );
25975                requests_made.fetch_add(1, atomic::Ordering::Release);
25976                Ok(vec![
25977                    lsp::ColorInformation {
25978                        range: lsp::Range {
25979                            start: lsp::Position {
25980                                line: 0,
25981                                character: 0,
25982                            },
25983                            end: lsp::Position {
25984                                line: 0,
25985                                character: 1,
25986                            },
25987                        },
25988                        color: lsp::Color {
25989                            red: 0.33,
25990                            green: 0.33,
25991                            blue: 0.33,
25992                            alpha: 0.33,
25993                        },
25994                    },
25995                    lsp::ColorInformation {
25996                        range: lsp::Range {
25997                            start: lsp::Position {
25998                                line: 0,
25999                                character: 0,
26000                            },
26001                            end: lsp::Position {
26002                                line: 0,
26003                                character: 1,
26004                            },
26005                        },
26006                        color: lsp::Color {
26007                            red: 0.33,
26008                            green: 0.33,
26009                            blue: 0.33,
26010                            alpha: 0.33,
26011                        },
26012                    },
26013                ])
26014            }
26015        });
26016
26017    let _handle = fake_language_server_without_capabilities
26018        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
26019            panic!("Should not be called");
26020        });
26021    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26022    color_request_handle.next().await.unwrap();
26023    cx.run_until_parked();
26024    assert_eq!(
26025        1,
26026        requests_made.load(atomic::Ordering::Acquire),
26027        "Should query for colors once per editor open"
26028    );
26029    editor.update_in(cx, |editor, _, cx| {
26030        assert_eq!(
26031            vec![expected_color],
26032            extract_color_inlays(editor, cx),
26033            "Should have an initial inlay"
26034        );
26035    });
26036
26037    // opening another file in a split should not influence the LSP query counter
26038    workspace
26039        .update(cx, |workspace, window, cx| {
26040            assert_eq!(
26041                workspace.panes().len(),
26042                1,
26043                "Should have one pane with one editor"
26044            );
26045            workspace.move_item_to_pane_in_direction(
26046                &MoveItemToPaneInDirection {
26047                    direction: SplitDirection::Right,
26048                    focus: false,
26049                    clone: true,
26050                },
26051                window,
26052                cx,
26053            );
26054        })
26055        .unwrap();
26056    cx.run_until_parked();
26057    workspace
26058        .update(cx, |workspace, _, cx| {
26059            let panes = workspace.panes();
26060            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
26061            for pane in panes {
26062                let editor = pane
26063                    .read(cx)
26064                    .active_item()
26065                    .and_then(|item| item.downcast::<Editor>())
26066                    .expect("Should have opened an editor in each split");
26067                let editor_file = editor
26068                    .read(cx)
26069                    .buffer()
26070                    .read(cx)
26071                    .as_singleton()
26072                    .expect("test deals with singleton buffers")
26073                    .read(cx)
26074                    .file()
26075                    .expect("test buffese should have a file")
26076                    .path();
26077                assert_eq!(
26078                    editor_file.as_ref(),
26079                    rel_path("first.rs"),
26080                    "Both editors should be opened for the same file"
26081                )
26082            }
26083        })
26084        .unwrap();
26085
26086    cx.executor().advance_clock(Duration::from_millis(500));
26087    let save = editor.update_in(cx, |editor, window, cx| {
26088        editor.move_to_end(&MoveToEnd, window, cx);
26089        editor.handle_input("dirty", window, cx);
26090        editor.save(
26091            SaveOptions {
26092                format: true,
26093                autosave: true,
26094            },
26095            project.clone(),
26096            window,
26097            cx,
26098        )
26099    });
26100    save.await.unwrap();
26101
26102    color_request_handle.next().await.unwrap();
26103    cx.run_until_parked();
26104    assert_eq!(
26105        2,
26106        requests_made.load(atomic::Ordering::Acquire),
26107        "Should query for colors once per save (deduplicated) and once per formatting after save"
26108    );
26109
26110    drop(editor);
26111    let close = workspace
26112        .update(cx, |workspace, window, cx| {
26113            workspace.active_pane().update(cx, |pane, cx| {
26114                pane.close_active_item(&CloseActiveItem::default(), window, cx)
26115            })
26116        })
26117        .unwrap();
26118    close.await.unwrap();
26119    let close = workspace
26120        .update(cx, |workspace, window, cx| {
26121            workspace.active_pane().update(cx, |pane, cx| {
26122                pane.close_active_item(&CloseActiveItem::default(), window, cx)
26123            })
26124        })
26125        .unwrap();
26126    close.await.unwrap();
26127    assert_eq!(
26128        2,
26129        requests_made.load(atomic::Ordering::Acquire),
26130        "After saving and closing all editors, no extra requests should be made"
26131    );
26132    workspace
26133        .update(cx, |workspace, _, cx| {
26134            assert!(
26135                workspace.active_item(cx).is_none(),
26136                "Should close all editors"
26137            )
26138        })
26139        .unwrap();
26140
26141    workspace
26142        .update(cx, |workspace, window, cx| {
26143            workspace.active_pane().update(cx, |pane, cx| {
26144                pane.navigate_backward(&workspace::GoBack, window, cx);
26145            })
26146        })
26147        .unwrap();
26148    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26149    cx.run_until_parked();
26150    let editor = workspace
26151        .update(cx, |workspace, _, cx| {
26152            workspace
26153                .active_item(cx)
26154                .expect("Should have reopened the editor again after navigating back")
26155                .downcast::<Editor>()
26156                .expect("Should be an editor")
26157        })
26158        .unwrap();
26159
26160    assert_eq!(
26161        2,
26162        requests_made.load(atomic::Ordering::Acquire),
26163        "Cache should be reused on buffer close and reopen"
26164    );
26165    editor.update(cx, |editor, cx| {
26166        assert_eq!(
26167            vec![expected_color],
26168            extract_color_inlays(editor, cx),
26169            "Should have an initial inlay"
26170        );
26171    });
26172
26173    drop(color_request_handle);
26174    let closure_requests_made = Arc::clone(&requests_made);
26175    let mut empty_color_request_handle = fake_language_server
26176        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
26177            let requests_made = Arc::clone(&closure_requests_made);
26178            async move {
26179                assert_eq!(
26180                    params.text_document.uri,
26181                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
26182                );
26183                requests_made.fetch_add(1, atomic::Ordering::Release);
26184                Ok(Vec::new())
26185            }
26186        });
26187    let save = editor.update_in(cx, |editor, window, cx| {
26188        editor.move_to_end(&MoveToEnd, window, cx);
26189        editor.handle_input("dirty_again", window, cx);
26190        editor.save(
26191            SaveOptions {
26192                format: false,
26193                autosave: true,
26194            },
26195            project.clone(),
26196            window,
26197            cx,
26198        )
26199    });
26200    save.await.unwrap();
26201
26202    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26203    empty_color_request_handle.next().await.unwrap();
26204    cx.run_until_parked();
26205    assert_eq!(
26206        3,
26207        requests_made.load(atomic::Ordering::Acquire),
26208        "Should query for colors once per save only, as formatting was not requested"
26209    );
26210    editor.update(cx, |editor, cx| {
26211        assert_eq!(
26212            Vec::<Rgba>::new(),
26213            extract_color_inlays(editor, cx),
26214            "Should clear all colors when the server returns an empty response"
26215        );
26216    });
26217}
26218
26219#[gpui::test]
26220async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
26221    init_test(cx, |_| {});
26222    let (editor, cx) = cx.add_window_view(Editor::single_line);
26223    editor.update_in(cx, |editor, window, cx| {
26224        editor.set_text("oops\n\nwow\n", window, cx)
26225    });
26226    cx.run_until_parked();
26227    editor.update(cx, |editor, cx| {
26228        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
26229    });
26230    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
26231    cx.run_until_parked();
26232    editor.update(cx, |editor, cx| {
26233        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
26234    });
26235}
26236
26237#[gpui::test]
26238async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
26239    init_test(cx, |_| {});
26240
26241    cx.update(|cx| {
26242        register_project_item::<Editor>(cx);
26243    });
26244
26245    let fs = FakeFs::new(cx.executor());
26246    fs.insert_tree("/root1", json!({})).await;
26247    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
26248        .await;
26249
26250    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
26251    let (workspace, cx) =
26252        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
26253
26254    let worktree_id = project.update(cx, |project, cx| {
26255        project.worktrees(cx).next().unwrap().read(cx).id()
26256    });
26257
26258    let handle = workspace
26259        .update_in(cx, |workspace, window, cx| {
26260            let project_path = (worktree_id, rel_path("one.pdf"));
26261            workspace.open_path(project_path, None, true, window, cx)
26262        })
26263        .await
26264        .unwrap();
26265
26266    assert_eq!(
26267        handle.to_any().entity_type(),
26268        TypeId::of::<InvalidItemView>()
26269    );
26270}
26271
26272#[gpui::test]
26273async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
26274    init_test(cx, |_| {});
26275
26276    let language = Arc::new(Language::new(
26277        LanguageConfig::default(),
26278        Some(tree_sitter_rust::LANGUAGE.into()),
26279    ));
26280
26281    // Test hierarchical sibling navigation
26282    let text = r#"
26283        fn outer() {
26284            if condition {
26285                let a = 1;
26286            }
26287            let b = 2;
26288        }
26289
26290        fn another() {
26291            let c = 3;
26292        }
26293    "#;
26294
26295    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
26296    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
26297    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
26298
26299    // Wait for parsing to complete
26300    editor
26301        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
26302        .await;
26303
26304    editor.update_in(cx, |editor, window, cx| {
26305        // Start by selecting "let a = 1;" inside the if block
26306        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26307            s.select_display_ranges([
26308                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
26309            ]);
26310        });
26311
26312        let initial_selection = editor
26313            .selections
26314            .display_ranges(&editor.display_snapshot(cx));
26315        assert_eq!(initial_selection.len(), 1, "Should have one selection");
26316
26317        // Test select next sibling - should move up levels to find the next sibling
26318        // Since "let a = 1;" has no siblings in the if block, it should move up
26319        // to find "let b = 2;" which is a sibling of the if block
26320        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
26321        let next_selection = editor
26322            .selections
26323            .display_ranges(&editor.display_snapshot(cx));
26324
26325        // Should have a selection and it should be different from the initial
26326        assert_eq!(
26327            next_selection.len(),
26328            1,
26329            "Should have one selection after next"
26330        );
26331        assert_ne!(
26332            next_selection[0], initial_selection[0],
26333            "Next sibling selection should be different"
26334        );
26335
26336        // Test hierarchical navigation by going to the end of the current function
26337        // and trying to navigate to the next function
26338        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26339            s.select_display_ranges([
26340                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
26341            ]);
26342        });
26343
26344        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
26345        let function_next_selection = editor
26346            .selections
26347            .display_ranges(&editor.display_snapshot(cx));
26348
26349        // Should move to the next function
26350        assert_eq!(
26351            function_next_selection.len(),
26352            1,
26353            "Should have one selection after function next"
26354        );
26355
26356        // Test select previous sibling navigation
26357        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
26358        let prev_selection = editor
26359            .selections
26360            .display_ranges(&editor.display_snapshot(cx));
26361
26362        // Should have a selection and it should be different
26363        assert_eq!(
26364            prev_selection.len(),
26365            1,
26366            "Should have one selection after prev"
26367        );
26368        assert_ne!(
26369            prev_selection[0], function_next_selection[0],
26370            "Previous sibling selection should be different from next"
26371        );
26372    });
26373}
26374
26375#[gpui::test]
26376async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
26377    init_test(cx, |_| {});
26378
26379    let mut cx = EditorTestContext::new(cx).await;
26380    cx.set_state(
26381        "let ˇvariable = 42;
26382let another = variable + 1;
26383let result = variable * 2;",
26384    );
26385
26386    // Set up document highlights manually (simulating LSP response)
26387    cx.update_editor(|editor, _window, cx| {
26388        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
26389
26390        // Create highlights for "variable" occurrences
26391        let highlight_ranges = [
26392            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
26393            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
26394            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
26395        ];
26396
26397        let anchor_ranges: Vec<_> = highlight_ranges
26398            .iter()
26399            .map(|range| range.clone().to_anchors(&buffer_snapshot))
26400            .collect();
26401
26402        editor.highlight_background::<DocumentHighlightRead>(
26403            &anchor_ranges,
26404            |theme| theme.colors().editor_document_highlight_read_background,
26405            cx,
26406        );
26407    });
26408
26409    // Go to next highlight - should move to second "variable"
26410    cx.update_editor(|editor, window, cx| {
26411        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26412    });
26413    cx.assert_editor_state(
26414        "let variable = 42;
26415let another = ˇvariable + 1;
26416let result = variable * 2;",
26417    );
26418
26419    // Go to next highlight - should move to third "variable"
26420    cx.update_editor(|editor, window, cx| {
26421        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26422    });
26423    cx.assert_editor_state(
26424        "let variable = 42;
26425let another = variable + 1;
26426let result = ˇvariable * 2;",
26427    );
26428
26429    // Go to next highlight - should stay at third "variable" (no wrap-around)
26430    cx.update_editor(|editor, window, cx| {
26431        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26432    });
26433    cx.assert_editor_state(
26434        "let variable = 42;
26435let another = variable + 1;
26436let result = ˇvariable * 2;",
26437    );
26438
26439    // Now test going backwards from third position
26440    cx.update_editor(|editor, window, cx| {
26441        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26442    });
26443    cx.assert_editor_state(
26444        "let variable = 42;
26445let another = ˇvariable + 1;
26446let result = variable * 2;",
26447    );
26448
26449    // Go to previous highlight - should move to first "variable"
26450    cx.update_editor(|editor, window, cx| {
26451        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26452    });
26453    cx.assert_editor_state(
26454        "let ˇvariable = 42;
26455let another = variable + 1;
26456let result = variable * 2;",
26457    );
26458
26459    // Go to previous highlight - should stay on first "variable"
26460    cx.update_editor(|editor, window, cx| {
26461        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26462    });
26463    cx.assert_editor_state(
26464        "let ˇvariable = 42;
26465let another = variable + 1;
26466let result = variable * 2;",
26467    );
26468}
26469
26470#[gpui::test]
26471async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
26472    cx: &mut gpui::TestAppContext,
26473) {
26474    init_test(cx, |_| {});
26475
26476    let url = "https://zed.dev";
26477
26478    let markdown_language = Arc::new(Language::new(
26479        LanguageConfig {
26480            name: "Markdown".into(),
26481            ..LanguageConfig::default()
26482        },
26483        None,
26484    ));
26485
26486    let mut cx = EditorTestContext::new(cx).await;
26487    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26488    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
26489
26490    cx.update_editor(|editor, window, cx| {
26491        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26492        editor.paste(&Paste, window, cx);
26493    });
26494
26495    cx.assert_editor_state(&format!(
26496        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)"
26497    ));
26498}
26499
26500#[gpui::test]
26501async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
26502    cx: &mut gpui::TestAppContext,
26503) {
26504    init_test(cx, |_| {});
26505
26506    let url = "https://zed.dev";
26507
26508    let markdown_language = Arc::new(Language::new(
26509        LanguageConfig {
26510            name: "Markdown".into(),
26511            ..LanguageConfig::default()
26512        },
26513        None,
26514    ));
26515
26516    let mut cx = EditorTestContext::new(cx).await;
26517    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26518    cx.set_state(&format!(
26519        "Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»"
26520    ));
26521
26522    cx.update_editor(|editor, window, cx| {
26523        editor.copy(&Copy, window, cx);
26524    });
26525
26526    cx.set_state(&format!(
26527        "Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}"
26528    ));
26529
26530    cx.update_editor(|editor, window, cx| {
26531        editor.paste(&Paste, window, cx);
26532    });
26533
26534    cx.assert_editor_state(&format!(
26535        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}"
26536    ));
26537}
26538
26539#[gpui::test]
26540async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link(
26541    cx: &mut gpui::TestAppContext,
26542) {
26543    init_test(cx, |_| {});
26544
26545    let url = "https://zed.dev";
26546
26547    let markdown_language = Arc::new(Language::new(
26548        LanguageConfig {
26549            name: "Markdown".into(),
26550            ..LanguageConfig::default()
26551        },
26552        None,
26553    ));
26554
26555    let mut cx = EditorTestContext::new(cx).await;
26556    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26557    cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»");
26558
26559    cx.update_editor(|editor, window, cx| {
26560        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26561        editor.paste(&Paste, window, cx);
26562    });
26563
26564    cx.assert_editor_state(&format!("Please visit zed's homepage: {url}ˇ"));
26565}
26566
26567#[gpui::test]
26568async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link(
26569    cx: &mut gpui::TestAppContext,
26570) {
26571    init_test(cx, |_| {});
26572
26573    let text = "Awesome";
26574
26575    let markdown_language = Arc::new(Language::new(
26576        LanguageConfig {
26577            name: "Markdown".into(),
26578            ..LanguageConfig::default()
26579        },
26580        None,
26581    ));
26582
26583    let mut cx = EditorTestContext::new(cx).await;
26584    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26585    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»");
26586
26587    cx.update_editor(|editor, window, cx| {
26588        cx.write_to_clipboard(ClipboardItem::new_string(text.to_string()));
26589        editor.paste(&Paste, window, cx);
26590    });
26591
26592    cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ"));
26593}
26594
26595#[gpui::test]
26596async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language(
26597    cx: &mut gpui::TestAppContext,
26598) {
26599    init_test(cx, |_| {});
26600
26601    let url = "https://zed.dev";
26602
26603    let markdown_language = Arc::new(Language::new(
26604        LanguageConfig {
26605            name: "Rust".into(),
26606            ..LanguageConfig::default()
26607        },
26608        None,
26609    ));
26610
26611    let mut cx = EditorTestContext::new(cx).await;
26612    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26613    cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
26614
26615    cx.update_editor(|editor, window, cx| {
26616        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26617        editor.paste(&Paste, window, cx);
26618    });
26619
26620    cx.assert_editor_state(&format!(
26621        "// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)"
26622    ));
26623}
26624
26625#[gpui::test]
26626async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer(
26627    cx: &mut TestAppContext,
26628) {
26629    init_test(cx, |_| {});
26630
26631    let url = "https://zed.dev";
26632
26633    let markdown_language = Arc::new(Language::new(
26634        LanguageConfig {
26635            name: "Markdown".into(),
26636            ..LanguageConfig::default()
26637        },
26638        None,
26639    ));
26640
26641    let (editor, cx) = cx.add_window_view(|window, cx| {
26642        let multi_buffer = MultiBuffer::build_multi(
26643            [
26644                ("this will embed -> link", vec![Point::row_range(0..1)]),
26645                ("this will replace -> link", vec![Point::row_range(0..1)]),
26646            ],
26647            cx,
26648        );
26649        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
26650        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26651            s.select_ranges(vec![
26652                Point::new(0, 19)..Point::new(0, 23),
26653                Point::new(1, 21)..Point::new(1, 25),
26654            ])
26655        });
26656        let first_buffer_id = multi_buffer
26657            .read(cx)
26658            .excerpt_buffer_ids()
26659            .into_iter()
26660            .next()
26661            .unwrap();
26662        let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
26663        first_buffer.update(cx, |buffer, cx| {
26664            buffer.set_language(Some(markdown_language.clone()), cx);
26665        });
26666
26667        editor
26668    });
26669    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
26670
26671    cx.update_editor(|editor, window, cx| {
26672        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26673        editor.paste(&Paste, window, cx);
26674    });
26675
26676    cx.assert_editor_state(&format!(
26677        "this will embed -> [link]({url}\nthis will replace -> {url}ˇ"
26678    ));
26679}
26680
26681#[gpui::test]
26682async fn test_race_in_multibuffer_save(cx: &mut TestAppContext) {
26683    init_test(cx, |_| {});
26684
26685    let fs = FakeFs::new(cx.executor());
26686    fs.insert_tree(
26687        path!("/project"),
26688        json!({
26689            "first.rs": "# First Document\nSome content here.",
26690            "second.rs": "Plain text content for second file.",
26691        }),
26692    )
26693    .await;
26694
26695    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
26696    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
26697    let cx = &mut VisualTestContext::from_window(*workspace, cx);
26698
26699    let language = rust_lang();
26700    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26701    language_registry.add(language.clone());
26702    let mut fake_servers = language_registry.register_fake_lsp(
26703        "Rust",
26704        FakeLspAdapter {
26705            ..FakeLspAdapter::default()
26706        },
26707    );
26708
26709    let buffer1 = project
26710        .update(cx, |project, cx| {
26711            project.open_local_buffer(PathBuf::from(path!("/project/first.rs")), cx)
26712        })
26713        .await
26714        .unwrap();
26715    let buffer2 = project
26716        .update(cx, |project, cx| {
26717            project.open_local_buffer(PathBuf::from(path!("/project/second.rs")), cx)
26718        })
26719        .await
26720        .unwrap();
26721
26722    let multi_buffer = cx.new(|cx| {
26723        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
26724        multi_buffer.set_excerpts_for_path(
26725            PathKey::for_buffer(&buffer1, cx),
26726            buffer1.clone(),
26727            [Point::zero()..buffer1.read(cx).max_point()],
26728            3,
26729            cx,
26730        );
26731        multi_buffer.set_excerpts_for_path(
26732            PathKey::for_buffer(&buffer2, cx),
26733            buffer2.clone(),
26734            [Point::zero()..buffer1.read(cx).max_point()],
26735            3,
26736            cx,
26737        );
26738        multi_buffer
26739    });
26740
26741    let (editor, cx) = cx.add_window_view(|window, cx| {
26742        Editor::new(
26743            EditorMode::full(),
26744            multi_buffer,
26745            Some(project.clone()),
26746            window,
26747            cx,
26748        )
26749    });
26750
26751    let fake_language_server = fake_servers.next().await.unwrap();
26752
26753    buffer1.update(cx, |buffer, cx| buffer.edit([(0..0, "hello!")], None, cx));
26754
26755    let save = editor.update_in(cx, |editor, window, cx| {
26756        assert!(editor.is_dirty(cx));
26757
26758        editor.save(
26759            SaveOptions {
26760                format: true,
26761                autosave: true,
26762            },
26763            project,
26764            window,
26765            cx,
26766        )
26767    });
26768    let (start_edit_tx, start_edit_rx) = oneshot::channel();
26769    let (done_edit_tx, done_edit_rx) = oneshot::channel();
26770    let mut done_edit_rx = Some(done_edit_rx);
26771    let mut start_edit_tx = Some(start_edit_tx);
26772
26773    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| {
26774        start_edit_tx.take().unwrap().send(()).unwrap();
26775        let done_edit_rx = done_edit_rx.take().unwrap();
26776        async move {
26777            done_edit_rx.await.unwrap();
26778            Ok(None)
26779        }
26780    });
26781
26782    start_edit_rx.await.unwrap();
26783    buffer2
26784        .update(cx, |buffer, cx| buffer.edit([(0..0, "world!")], None, cx))
26785        .unwrap();
26786
26787    done_edit_tx.send(()).unwrap();
26788
26789    save.await.unwrap();
26790    cx.update(|_, cx| assert!(editor.is_dirty(cx)));
26791}
26792
26793#[track_caller]
26794fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
26795    editor
26796        .all_inlays(cx)
26797        .into_iter()
26798        .filter_map(|inlay| inlay.get_color())
26799        .map(Rgba::from)
26800        .collect()
26801}
26802
26803#[gpui::test]
26804fn test_duplicate_line_up_on_last_line_without_newline(cx: &mut TestAppContext) {
26805    init_test(cx, |_| {});
26806
26807    let editor = cx.add_window(|window, cx| {
26808        let buffer = MultiBuffer::build_simple("line1\nline2", cx);
26809        build_editor(buffer, window, cx)
26810    });
26811
26812    editor
26813        .update(cx, |editor, window, cx| {
26814            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26815                s.select_display_ranges([
26816                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
26817                ])
26818            });
26819
26820            editor.duplicate_line_up(&DuplicateLineUp, window, cx);
26821
26822            assert_eq!(
26823                editor.display_text(cx),
26824                "line1\nline2\nline2",
26825                "Duplicating last line upward should create duplicate above, not on same line"
26826            );
26827
26828            assert_eq!(
26829                editor
26830                    .selections
26831                    .display_ranges(&editor.display_snapshot(cx)),
26832                vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)],
26833                "Selection should move to the duplicated line"
26834            );
26835        })
26836        .unwrap();
26837}
26838
26839#[gpui::test]
26840async fn test_copy_line_without_trailing_newline(cx: &mut TestAppContext) {
26841    init_test(cx, |_| {});
26842
26843    let mut cx = EditorTestContext::new(cx).await;
26844
26845    cx.set_state("line1\nline2ˇ");
26846
26847    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
26848
26849    let clipboard_text = cx
26850        .read_from_clipboard()
26851        .and_then(|item| item.text().as_deref().map(str::to_string));
26852
26853    assert_eq!(
26854        clipboard_text,
26855        Some("line2\n".to_string()),
26856        "Copying a line without trailing newline should include a newline"
26857    );
26858
26859    cx.set_state("line1\nˇ");
26860
26861    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
26862
26863    cx.assert_editor_state("line1\nline2\nˇ");
26864}
26865
26866#[gpui::test]
26867async fn test_end_of_editor_context(cx: &mut TestAppContext) {
26868    init_test(cx, |_| {});
26869
26870    let mut cx = EditorTestContext::new(cx).await;
26871
26872    cx.set_state("line1\nline2ˇ");
26873    cx.update_editor(|e, window, cx| {
26874        e.set_mode(EditorMode::SingleLine);
26875        assert!(e.key_context(window, cx).contains("end_of_input"));
26876    });
26877    cx.set_state("ˇline1\nline2");
26878    cx.update_editor(|e, window, cx| {
26879        assert!(!e.key_context(window, cx).contains("end_of_input"));
26880    });
26881    cx.set_state("line1ˇ\nline2");
26882    cx.update_editor(|e, window, cx| {
26883        assert!(!e.key_context(window, cx).contains("end_of_input"));
26884    });
26885}
26886
26887#[gpui::test]
26888async fn test_next_prev_reference(cx: &mut TestAppContext) {
26889    const CYCLE_POSITIONS: &[&'static str] = &[
26890        indoc! {"
26891            fn foo() {
26892                let ˇabc = 123;
26893                let x = abc + 1;
26894                let y = abc + 2;
26895                let z = abc + 2;
26896            }
26897        "},
26898        indoc! {"
26899            fn foo() {
26900                let abc = 123;
26901                let x = ˇabc + 1;
26902                let y = abc + 2;
26903                let z = abc + 2;
26904            }
26905        "},
26906        indoc! {"
26907            fn foo() {
26908                let abc = 123;
26909                let x = abc + 1;
26910                let y = ˇabc + 2;
26911                let z = abc + 2;
26912            }
26913        "},
26914        indoc! {"
26915            fn foo() {
26916                let abc = 123;
26917                let x = abc + 1;
26918                let y = abc + 2;
26919                let z = ˇabc + 2;
26920            }
26921        "},
26922    ];
26923
26924    init_test(cx, |_| {});
26925
26926    let mut cx = EditorLspTestContext::new_rust(
26927        lsp::ServerCapabilities {
26928            references_provider: Some(lsp::OneOf::Left(true)),
26929            ..Default::default()
26930        },
26931        cx,
26932    )
26933    .await;
26934
26935    // importantly, the cursor is in the middle
26936    cx.set_state(indoc! {"
26937        fn foo() {
26938            let aˇbc = 123;
26939            let x = abc + 1;
26940            let y = abc + 2;
26941            let z = abc + 2;
26942        }
26943    "});
26944
26945    let reference_ranges = [
26946        lsp::Position::new(1, 8),
26947        lsp::Position::new(2, 12),
26948        lsp::Position::new(3, 12),
26949        lsp::Position::new(4, 12),
26950    ]
26951    .map(|start| lsp::Range::new(start, lsp::Position::new(start.line, start.character + 3)));
26952
26953    cx.lsp
26954        .set_request_handler::<lsp::request::References, _, _>(move |params, _cx| async move {
26955            Ok(Some(
26956                reference_ranges
26957                    .map(|range| lsp::Location {
26958                        uri: params.text_document_position.text_document.uri.clone(),
26959                        range,
26960                    })
26961                    .to_vec(),
26962            ))
26963        });
26964
26965    let _move = async |direction, count, cx: &mut EditorLspTestContext| {
26966        cx.update_editor(|editor, window, cx| {
26967            editor.go_to_reference_before_or_after_position(direction, count, window, cx)
26968        })
26969        .unwrap()
26970        .await
26971        .unwrap()
26972    };
26973
26974    _move(Direction::Next, 1, &mut cx).await;
26975    cx.assert_editor_state(CYCLE_POSITIONS[1]);
26976
26977    _move(Direction::Next, 1, &mut cx).await;
26978    cx.assert_editor_state(CYCLE_POSITIONS[2]);
26979
26980    _move(Direction::Next, 1, &mut cx).await;
26981    cx.assert_editor_state(CYCLE_POSITIONS[3]);
26982
26983    // loops back to the start
26984    _move(Direction::Next, 1, &mut cx).await;
26985    cx.assert_editor_state(CYCLE_POSITIONS[0]);
26986
26987    // loops back to the end
26988    _move(Direction::Prev, 1, &mut cx).await;
26989    cx.assert_editor_state(CYCLE_POSITIONS[3]);
26990
26991    _move(Direction::Prev, 1, &mut cx).await;
26992    cx.assert_editor_state(CYCLE_POSITIONS[2]);
26993
26994    _move(Direction::Prev, 1, &mut cx).await;
26995    cx.assert_editor_state(CYCLE_POSITIONS[1]);
26996
26997    _move(Direction::Prev, 1, &mut cx).await;
26998    cx.assert_editor_state(CYCLE_POSITIONS[0]);
26999
27000    _move(Direction::Next, 3, &mut cx).await;
27001    cx.assert_editor_state(CYCLE_POSITIONS[3]);
27002
27003    _move(Direction::Prev, 2, &mut cx).await;
27004    cx.assert_editor_state(CYCLE_POSITIONS[1]);
27005}