editor_tests.rs

    1use super::*;
    2use crate::{
    3    JoinLines,
    4    code_context_menus::CodeContextMenu,
    5    edit_prediction_tests::FakeEditPredictionProvider,
    6    element::StickyHeader,
    7    linked_editing_ranges::LinkedEditingRanges,
    8    scroll::scroll_amount::ScrollAmount,
    9    test::{
   10        assert_text_with_selections, build_editor,
   11        editor_lsp_test_context::{EditorLspTestContext, git_commit_lang},
   12        editor_test_context::EditorTestContext,
   13        select_ranges,
   14    },
   15};
   16use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
   17use collections::HashMap;
   18use futures::{StreamExt, channel::oneshot};
   19use gpui::{
   20    BackgroundExecutor, DismissEvent, Rgba, SemanticVersion, TestAppContext, UpdateGlobal,
   21    VisualTestContext, WindowBounds, WindowOptions, div,
   22};
   23use indoc::indoc;
   24use language::{
   25    BracketPairConfig,
   26    Capability::ReadWrite,
   27    DiagnosticSourceKind, FakeLspAdapter, IndentGuideSettings, LanguageConfig,
   28    LanguageConfigOverride, LanguageMatcher, LanguageName, Override, Point,
   29    language_settings::{
   30        CompletionSettingsContent, FormatterList, LanguageSettingsContent, LspInsertMode,
   31    },
   32    tree_sitter_python,
   33};
   34use language_settings::Formatter;
   35use languages::rust_lang;
   36use lsp::CompletionParams;
   37use multi_buffer::{IndentGuide, PathKey};
   38use parking_lot::Mutex;
   39use pretty_assertions::{assert_eq, assert_ne};
   40use project::{
   41    FakeFs,
   42    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   43    project_settings::LspSettings,
   44};
   45use serde_json::{self, json};
   46use settings::{
   47    AllLanguageSettingsContent, IndentGuideBackgroundColoring, IndentGuideColoring,
   48    ProjectSettingsContent,
   49};
   50use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   51use std::{
   52    iter,
   53    sync::atomic::{self, AtomicUsize},
   54};
   55use test::build_editor_with_project;
   56use text::ToPoint as _;
   57use unindent::Unindent;
   58use util::{
   59    assert_set_eq, path,
   60    rel_path::rel_path,
   61    test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
   62    uri,
   63};
   64use workspace::{
   65    CloseActiveItem, CloseAllItems, CloseOtherItems, MoveItemToPaneInDirection, NavigationEntry,
   66    OpenOptions, ViewId,
   67    invalid_item_view::InvalidItemView,
   68    item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions},
   69    register_project_item,
   70};
   71
   72fn display_ranges(editor: &Editor, cx: &mut Context<'_, Editor>) -> Vec<Range<DisplayPoint>> {
   73    editor
   74        .selections
   75        .display_ranges(&editor.display_snapshot(cx))
   76}
   77
   78#[gpui::test]
   79fn test_edit_events(cx: &mut TestAppContext) {
   80    init_test(cx, |_| {});
   81
   82    let buffer = cx.new(|cx| {
   83        let mut buffer = language::Buffer::local("123456", cx);
   84        buffer.set_group_interval(Duration::from_secs(1));
   85        buffer
   86    });
   87
   88    let events = Rc::new(RefCell::new(Vec::new()));
   89    let editor1 = cx.add_window({
   90        let events = events.clone();
   91        |window, cx| {
   92            let entity = cx.entity();
   93            cx.subscribe_in(
   94                &entity,
   95                window,
   96                move |_, _, event: &EditorEvent, _, _| match event {
   97                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
   98                    EditorEvent::BufferEdited => {
   99                        events.borrow_mut().push(("editor1", "buffer edited"))
  100                    }
  101                    _ => {}
  102                },
  103            )
  104            .detach();
  105            Editor::for_buffer(buffer.clone(), None, window, cx)
  106        }
  107    });
  108
  109    let editor2 = cx.add_window({
  110        let events = events.clone();
  111        |window, cx| {
  112            cx.subscribe_in(
  113                &cx.entity(),
  114                window,
  115                move |_, _, event: &EditorEvent, _, _| match event {
  116                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
  117                    EditorEvent::BufferEdited => {
  118                        events.borrow_mut().push(("editor2", "buffer edited"))
  119                    }
  120                    _ => {}
  121                },
  122            )
  123            .detach();
  124            Editor::for_buffer(buffer.clone(), None, window, cx)
  125        }
  126    });
  127
  128    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  129
  130    // Mutating editor 1 will emit an `Edited` event only for that editor.
  131    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  132    assert_eq!(
  133        mem::take(&mut *events.borrow_mut()),
  134        [
  135            ("editor1", "edited"),
  136            ("editor1", "buffer edited"),
  137            ("editor2", "buffer edited"),
  138        ]
  139    );
  140
  141    // Mutating editor 2 will emit an `Edited` event only for that editor.
  142    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  143    assert_eq!(
  144        mem::take(&mut *events.borrow_mut()),
  145        [
  146            ("editor2", "edited"),
  147            ("editor1", "buffer edited"),
  148            ("editor2", "buffer edited"),
  149        ]
  150    );
  151
  152    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  153    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  154    assert_eq!(
  155        mem::take(&mut *events.borrow_mut()),
  156        [
  157            ("editor1", "edited"),
  158            ("editor1", "buffer edited"),
  159            ("editor2", "buffer edited"),
  160        ]
  161    );
  162
  163    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  164    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  165    assert_eq!(
  166        mem::take(&mut *events.borrow_mut()),
  167        [
  168            ("editor1", "edited"),
  169            ("editor1", "buffer edited"),
  170            ("editor2", "buffer edited"),
  171        ]
  172    );
  173
  174    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  175    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  176    assert_eq!(
  177        mem::take(&mut *events.borrow_mut()),
  178        [
  179            ("editor2", "edited"),
  180            ("editor1", "buffer edited"),
  181            ("editor2", "buffer edited"),
  182        ]
  183    );
  184
  185    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  186    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  187    assert_eq!(
  188        mem::take(&mut *events.borrow_mut()),
  189        [
  190            ("editor2", "edited"),
  191            ("editor1", "buffer edited"),
  192            ("editor2", "buffer edited"),
  193        ]
  194    );
  195
  196    // No event is emitted when the mutation is a no-op.
  197    _ = editor2.update(cx, |editor, window, cx| {
  198        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  199            s.select_ranges([0..0])
  200        });
  201
  202        editor.backspace(&Backspace, window, cx);
  203    });
  204    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  205}
  206
  207#[gpui::test]
  208fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  209    init_test(cx, |_| {});
  210
  211    let mut now = Instant::now();
  212    let group_interval = Duration::from_millis(1);
  213    let buffer = cx.new(|cx| {
  214        let mut buf = language::Buffer::local("123456", cx);
  215        buf.set_group_interval(group_interval);
  216        buf
  217    });
  218    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  219    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  220
  221    _ = editor.update(cx, |editor, window, cx| {
  222        editor.start_transaction_at(now, window, cx);
  223        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  224            s.select_ranges([2..4])
  225        });
  226
  227        editor.insert("cd", window, cx);
  228        editor.end_transaction_at(now, cx);
  229        assert_eq!(editor.text(cx), "12cd56");
  230        assert_eq!(
  231            editor.selections.ranges(&editor.display_snapshot(cx)),
  232            vec![4..4]
  233        );
  234
  235        editor.start_transaction_at(now, window, cx);
  236        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  237            s.select_ranges([4..5])
  238        });
  239        editor.insert("e", window, cx);
  240        editor.end_transaction_at(now, cx);
  241        assert_eq!(editor.text(cx), "12cde6");
  242        assert_eq!(
  243            editor.selections.ranges(&editor.display_snapshot(cx)),
  244            vec![5..5]
  245        );
  246
  247        now += group_interval + Duration::from_millis(1);
  248        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  249            s.select_ranges([2..2])
  250        });
  251
  252        // Simulate an edit in another editor
  253        buffer.update(cx, |buffer, cx| {
  254            buffer.start_transaction_at(now, cx);
  255            buffer.edit([(0..1, "a")], None, cx);
  256            buffer.edit([(1..1, "b")], None, cx);
  257            buffer.end_transaction_at(now, cx);
  258        });
  259
  260        assert_eq!(editor.text(cx), "ab2cde6");
  261        assert_eq!(
  262            editor.selections.ranges(&editor.display_snapshot(cx)),
  263            vec![3..3]
  264        );
  265
  266        // Last transaction happened past the group interval in a different editor.
  267        // Undo it individually and don't restore selections.
  268        editor.undo(&Undo, window, cx);
  269        assert_eq!(editor.text(cx), "12cde6");
  270        assert_eq!(
  271            editor.selections.ranges(&editor.display_snapshot(cx)),
  272            vec![2..2]
  273        );
  274
  275        // First two transactions happened within the group interval in this editor.
  276        // Undo them together and restore selections.
  277        editor.undo(&Undo, window, cx);
  278        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  279        assert_eq!(editor.text(cx), "123456");
  280        assert_eq!(
  281            editor.selections.ranges(&editor.display_snapshot(cx)),
  282            vec![0..0]
  283        );
  284
  285        // Redo the first two transactions together.
  286        editor.redo(&Redo, window, cx);
  287        assert_eq!(editor.text(cx), "12cde6");
  288        assert_eq!(
  289            editor.selections.ranges(&editor.display_snapshot(cx)),
  290            vec![5..5]
  291        );
  292
  293        // Redo the last transaction on its own.
  294        editor.redo(&Redo, window, cx);
  295        assert_eq!(editor.text(cx), "ab2cde6");
  296        assert_eq!(
  297            editor.selections.ranges(&editor.display_snapshot(cx)),
  298            vec![6..6]
  299        );
  300
  301        // Test empty transactions.
  302        editor.start_transaction_at(now, window, cx);
  303        editor.end_transaction_at(now, cx);
  304        editor.undo(&Undo, window, cx);
  305        assert_eq!(editor.text(cx), "12cde6");
  306    });
  307}
  308
  309#[gpui::test]
  310fn test_ime_composition(cx: &mut TestAppContext) {
  311    init_test(cx, |_| {});
  312
  313    let buffer = cx.new(|cx| {
  314        let mut buffer = language::Buffer::local("abcde", cx);
  315        // Ensure automatic grouping doesn't occur.
  316        buffer.set_group_interval(Duration::ZERO);
  317        buffer
  318    });
  319
  320    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  321    cx.add_window(|window, cx| {
  322        let mut editor = build_editor(buffer.clone(), window, cx);
  323
  324        // Start a new IME composition.
  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        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  328        assert_eq!(editor.text(cx), "äbcde");
  329        assert_eq!(
  330            editor.marked_text_ranges(cx),
  331            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  332        );
  333
  334        // Finalize IME composition.
  335        editor.replace_text_in_range(None, "ā", window, cx);
  336        assert_eq!(editor.text(cx), "ābcde");
  337        assert_eq!(editor.marked_text_ranges(cx), None);
  338
  339        // IME composition edits are grouped and are undone/redone at once.
  340        editor.undo(&Default::default(), window, cx);
  341        assert_eq!(editor.text(cx), "abcde");
  342        assert_eq!(editor.marked_text_ranges(cx), None);
  343        editor.redo(&Default::default(), window, cx);
  344        assert_eq!(editor.text(cx), "ābcde");
  345        assert_eq!(editor.marked_text_ranges(cx), None);
  346
  347        // Start a new IME composition.
  348        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  349        assert_eq!(
  350            editor.marked_text_ranges(cx),
  351            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  352        );
  353
  354        // Undoing during an IME composition cancels it.
  355        editor.undo(&Default::default(), window, cx);
  356        assert_eq!(editor.text(cx), "ābcde");
  357        assert_eq!(editor.marked_text_ranges(cx), None);
  358
  359        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  360        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  361        assert_eq!(editor.text(cx), "ābcdè");
  362        assert_eq!(
  363            editor.marked_text_ranges(cx),
  364            Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
  365        );
  366
  367        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  368        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  369        assert_eq!(editor.text(cx), "ābcdę");
  370        assert_eq!(editor.marked_text_ranges(cx), None);
  371
  372        // Start a new IME composition with multiple cursors.
  373        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  374            s.select_ranges([
  375                OffsetUtf16(1)..OffsetUtf16(1),
  376                OffsetUtf16(3)..OffsetUtf16(3),
  377                OffsetUtf16(5)..OffsetUtf16(5),
  378            ])
  379        });
  380        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  381        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  382        assert_eq!(
  383            editor.marked_text_ranges(cx),
  384            Some(vec![
  385                OffsetUtf16(0)..OffsetUtf16(3),
  386                OffsetUtf16(4)..OffsetUtf16(7),
  387                OffsetUtf16(8)..OffsetUtf16(11)
  388            ])
  389        );
  390
  391        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  392        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  393        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  394        assert_eq!(
  395            editor.marked_text_ranges(cx),
  396            Some(vec![
  397                OffsetUtf16(1)..OffsetUtf16(2),
  398                OffsetUtf16(5)..OffsetUtf16(6),
  399                OffsetUtf16(9)..OffsetUtf16(10)
  400            ])
  401        );
  402
  403        // Finalize IME composition with multiple cursors.
  404        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  405        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  406        assert_eq!(editor.marked_text_ranges(cx), None);
  407
  408        editor
  409    });
  410}
  411
  412#[gpui::test]
  413fn test_selection_with_mouse(cx: &mut TestAppContext) {
  414    init_test(cx, |_| {});
  415
  416    let editor = cx.add_window(|window, cx| {
  417        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  418        build_editor(buffer, window, cx)
  419    });
  420
  421    _ = editor.update(cx, |editor, window, cx| {
  422        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  423    });
  424    assert_eq!(
  425        editor
  426            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  427            .unwrap(),
  428        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  429    );
  430
  431    _ = editor.update(cx, |editor, window, cx| {
  432        editor.update_selection(
  433            DisplayPoint::new(DisplayRow(3), 3),
  434            0,
  435            gpui::Point::<f32>::default(),
  436            window,
  437            cx,
  438        );
  439    });
  440
  441    assert_eq!(
  442        editor
  443            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  444            .unwrap(),
  445        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  446    );
  447
  448    _ = editor.update(cx, |editor, window, cx| {
  449        editor.update_selection(
  450            DisplayPoint::new(DisplayRow(1), 1),
  451            0,
  452            gpui::Point::<f32>::default(),
  453            window,
  454            cx,
  455        );
  456    });
  457
  458    assert_eq!(
  459        editor
  460            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  461            .unwrap(),
  462        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  463    );
  464
  465    _ = editor.update(cx, |editor, window, cx| {
  466        editor.end_selection(window, cx);
  467        editor.update_selection(
  468            DisplayPoint::new(DisplayRow(3), 3),
  469            0,
  470            gpui::Point::<f32>::default(),
  471            window,
  472            cx,
  473        );
  474    });
  475
  476    assert_eq!(
  477        editor
  478            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  479            .unwrap(),
  480        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  481    );
  482
  483    _ = editor.update(cx, |editor, window, cx| {
  484        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  485        editor.update_selection(
  486            DisplayPoint::new(DisplayRow(0), 0),
  487            0,
  488            gpui::Point::<f32>::default(),
  489            window,
  490            cx,
  491        );
  492    });
  493
  494    assert_eq!(
  495        editor
  496            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  497            .unwrap(),
  498        [
  499            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  500            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  501        ]
  502    );
  503
  504    _ = editor.update(cx, |editor, window, cx| {
  505        editor.end_selection(window, cx);
  506    });
  507
  508    assert_eq!(
  509        editor
  510            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  511            .unwrap(),
  512        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  513    );
  514}
  515
  516#[gpui::test]
  517fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  518    init_test(cx, |_| {});
  519
  520    let editor = cx.add_window(|window, cx| {
  521        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  522        build_editor(buffer, window, cx)
  523    });
  524
  525    _ = editor.update(cx, |editor, window, cx| {
  526        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  527    });
  528
  529    _ = editor.update(cx, |editor, window, cx| {
  530        editor.end_selection(window, cx);
  531    });
  532
  533    _ = editor.update(cx, |editor, window, cx| {
  534        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  535    });
  536
  537    _ = editor.update(cx, |editor, window, cx| {
  538        editor.end_selection(window, cx);
  539    });
  540
  541    assert_eq!(
  542        editor
  543            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  544            .unwrap(),
  545        [
  546            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  547            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  548        ]
  549    );
  550
  551    _ = editor.update(cx, |editor, window, cx| {
  552        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  553    });
  554
  555    _ = editor.update(cx, |editor, window, cx| {
  556        editor.end_selection(window, cx);
  557    });
  558
  559    assert_eq!(
  560        editor
  561            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  562            .unwrap(),
  563        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  564    );
  565}
  566
  567#[gpui::test]
  568fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  569    init_test(cx, |_| {});
  570
  571    let editor = cx.add_window(|window, cx| {
  572        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  573        build_editor(buffer, window, cx)
  574    });
  575
  576    _ = editor.update(cx, |editor, window, cx| {
  577        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  578        assert_eq!(
  579            display_ranges(editor, cx),
  580            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  581        );
  582    });
  583
  584    _ = editor.update(cx, |editor, window, cx| {
  585        editor.update_selection(
  586            DisplayPoint::new(DisplayRow(3), 3),
  587            0,
  588            gpui::Point::<f32>::default(),
  589            window,
  590            cx,
  591        );
  592        assert_eq!(
  593            display_ranges(editor, cx),
  594            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  595        );
  596    });
  597
  598    _ = editor.update(cx, |editor, window, cx| {
  599        editor.cancel(&Cancel, window, cx);
  600        editor.update_selection(
  601            DisplayPoint::new(DisplayRow(1), 1),
  602            0,
  603            gpui::Point::<f32>::default(),
  604            window,
  605            cx,
  606        );
  607        assert_eq!(
  608            display_ranges(editor, cx),
  609            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  610        );
  611    });
  612}
  613
  614#[gpui::test]
  615fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  616    init_test(cx, |_| {});
  617
  618    let editor = cx.add_window(|window, cx| {
  619        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  620        build_editor(buffer, window, cx)
  621    });
  622
  623    _ = editor.update(cx, |editor, window, cx| {
  624        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  625        assert_eq!(
  626            display_ranges(editor, cx),
  627            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  628        );
  629
  630        editor.move_down(&Default::default(), window, cx);
  631        assert_eq!(
  632            display_ranges(editor, cx),
  633            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  634        );
  635
  636        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  637        assert_eq!(
  638            display_ranges(editor, cx),
  639            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  640        );
  641
  642        editor.move_up(&Default::default(), window, cx);
  643        assert_eq!(
  644            display_ranges(editor, cx),
  645            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  646        );
  647    });
  648}
  649
  650#[gpui::test]
  651fn test_extending_selection(cx: &mut TestAppContext) {
  652    init_test(cx, |_| {});
  653
  654    let editor = cx.add_window(|window, cx| {
  655        let buffer = MultiBuffer::build_simple("aaa bbb ccc ddd eee", cx);
  656        build_editor(buffer, window, cx)
  657    });
  658
  659    _ = editor.update(cx, |editor, window, cx| {
  660        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), false, 1, window, cx);
  661        editor.end_selection(window, cx);
  662        assert_eq!(
  663            display_ranges(editor, cx),
  664            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)]
  665        );
  666
  667        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  668        editor.end_selection(window, cx);
  669        assert_eq!(
  670            display_ranges(editor, cx),
  671            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 10)]
  672        );
  673
  674        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  675        editor.end_selection(window, cx);
  676        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 2, window, cx);
  677        assert_eq!(
  678            display_ranges(editor, cx),
  679            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 11)]
  680        );
  681
  682        editor.update_selection(
  683            DisplayPoint::new(DisplayRow(0), 1),
  684            0,
  685            gpui::Point::<f32>::default(),
  686            window,
  687            cx,
  688        );
  689        editor.end_selection(window, cx);
  690        assert_eq!(
  691            display_ranges(editor, cx),
  692            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 0)]
  693        );
  694
  695        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), true, 1, window, cx);
  696        editor.end_selection(window, cx);
  697        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), true, 2, window, cx);
  698        editor.end_selection(window, cx);
  699        assert_eq!(
  700            display_ranges(editor, cx),
  701            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 7)]
  702        );
  703
  704        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  705        assert_eq!(
  706            display_ranges(editor, cx),
  707            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 11)]
  708        );
  709
  710        editor.update_selection(
  711            DisplayPoint::new(DisplayRow(0), 6),
  712            0,
  713            gpui::Point::<f32>::default(),
  714            window,
  715            cx,
  716        );
  717        assert_eq!(
  718            display_ranges(editor, cx),
  719            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 7)]
  720        );
  721
  722        editor.update_selection(
  723            DisplayPoint::new(DisplayRow(0), 1),
  724            0,
  725            gpui::Point::<f32>::default(),
  726            window,
  727            cx,
  728        );
  729        editor.end_selection(window, cx);
  730        assert_eq!(
  731            display_ranges(editor, cx),
  732            [DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 0)]
  733        );
  734    });
  735}
  736
  737#[gpui::test]
  738fn test_clone(cx: &mut TestAppContext) {
  739    init_test(cx, |_| {});
  740
  741    let (text, selection_ranges) = marked_text_ranges(
  742        indoc! {"
  743            one
  744            two
  745            threeˇ
  746            four
  747            fiveˇ
  748        "},
  749        true,
  750    );
  751
  752    let editor = cx.add_window(|window, cx| {
  753        let buffer = MultiBuffer::build_simple(&text, cx);
  754        build_editor(buffer, window, cx)
  755    });
  756
  757    _ = editor.update(cx, |editor, window, cx| {
  758        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  759            s.select_ranges(selection_ranges.clone())
  760        });
  761        editor.fold_creases(
  762            vec![
  763                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  764                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  765            ],
  766            true,
  767            window,
  768            cx,
  769        );
  770    });
  771
  772    let cloned_editor = editor
  773        .update(cx, |editor, _, cx| {
  774            cx.open_window(Default::default(), |window, cx| {
  775                cx.new(|cx| editor.clone(window, cx))
  776            })
  777        })
  778        .unwrap()
  779        .unwrap();
  780
  781    let snapshot = editor
  782        .update(cx, |e, window, cx| e.snapshot(window, cx))
  783        .unwrap();
  784    let cloned_snapshot = cloned_editor
  785        .update(cx, |e, window, cx| e.snapshot(window, cx))
  786        .unwrap();
  787
  788    assert_eq!(
  789        cloned_editor
  790            .update(cx, |e, _, cx| e.display_text(cx))
  791            .unwrap(),
  792        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  793    );
  794    assert_eq!(
  795        cloned_snapshot
  796            .folds_in_range(0..text.len())
  797            .collect::<Vec<_>>(),
  798        snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
  799    );
  800    assert_set_eq!(
  801        cloned_editor
  802            .update(cx, |editor, _, cx| editor
  803                .selections
  804                .ranges::<Point>(&editor.display_snapshot(cx)))
  805            .unwrap(),
  806        editor
  807            .update(cx, |editor, _, cx| editor
  808                .selections
  809                .ranges(&editor.display_snapshot(cx)))
  810            .unwrap()
  811    );
  812    assert_set_eq!(
  813        cloned_editor
  814            .update(cx, |e, _window, cx| e
  815                .selections
  816                .display_ranges(&e.display_snapshot(cx)))
  817            .unwrap(),
  818        editor
  819            .update(cx, |e, _, cx| e
  820                .selections
  821                .display_ranges(&e.display_snapshot(cx)))
  822            .unwrap()
  823    );
  824}
  825
  826#[gpui::test]
  827async fn test_navigation_history(cx: &mut TestAppContext) {
  828    init_test(cx, |_| {});
  829
  830    use workspace::item::Item;
  831
  832    let fs = FakeFs::new(cx.executor());
  833    let project = Project::test(fs, [], cx).await;
  834    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  835    let pane = workspace
  836        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  837        .unwrap();
  838
  839    _ = workspace.update(cx, |_v, window, cx| {
  840        cx.new(|cx| {
  841            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  842            let mut editor = build_editor(buffer, window, cx);
  843            let handle = cx.entity();
  844            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  845
  846            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  847                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  848            }
  849
  850            // Move the cursor a small distance.
  851            // Nothing is added to the navigation history.
  852            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  853                s.select_display_ranges([
  854                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  855                ])
  856            });
  857            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  858                s.select_display_ranges([
  859                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  860                ])
  861            });
  862            assert!(pop_history(&mut editor, cx).is_none());
  863
  864            // Move the cursor a large distance.
  865            // The history can jump back to the previous position.
  866            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  867                s.select_display_ranges([
  868                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  869                ])
  870            });
  871            let nav_entry = pop_history(&mut editor, cx).unwrap();
  872            editor.navigate(nav_entry.data.unwrap(), window, cx);
  873            assert_eq!(nav_entry.item.id(), cx.entity_id());
  874            assert_eq!(
  875                editor
  876                    .selections
  877                    .display_ranges(&editor.display_snapshot(cx)),
  878                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  879            );
  880            assert!(pop_history(&mut editor, cx).is_none());
  881
  882            // Move the cursor a small distance via the mouse.
  883            // Nothing is added to the navigation history.
  884            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  885            editor.end_selection(window, cx);
  886            assert_eq!(
  887                editor
  888                    .selections
  889                    .display_ranges(&editor.display_snapshot(cx)),
  890                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  891            );
  892            assert!(pop_history(&mut editor, cx).is_none());
  893
  894            // Move the cursor a large distance via the mouse.
  895            // The history can jump back to the previous position.
  896            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  897            editor.end_selection(window, cx);
  898            assert_eq!(
  899                editor
  900                    .selections
  901                    .display_ranges(&editor.display_snapshot(cx)),
  902                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  903            );
  904            let nav_entry = pop_history(&mut editor, cx).unwrap();
  905            editor.navigate(nav_entry.data.unwrap(), window, cx);
  906            assert_eq!(nav_entry.item.id(), cx.entity_id());
  907            assert_eq!(
  908                editor
  909                    .selections
  910                    .display_ranges(&editor.display_snapshot(cx)),
  911                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  912            );
  913            assert!(pop_history(&mut editor, cx).is_none());
  914
  915            // Set scroll position to check later
  916            editor.set_scroll_position(gpui::Point::<f64>::new(5.5, 5.5), window, cx);
  917            let original_scroll_position = editor.scroll_manager.anchor();
  918
  919            // Jump to the end of the document and adjust scroll
  920            editor.move_to_end(&MoveToEnd, window, cx);
  921            editor.set_scroll_position(gpui::Point::<f64>::new(-2.5, -0.5), window, cx);
  922            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  923
  924            let nav_entry = pop_history(&mut editor, cx).unwrap();
  925            editor.navigate(nav_entry.data.unwrap(), window, cx);
  926            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  927
  928            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  929            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  930            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  931            let invalid_point = Point::new(9999, 0);
  932            editor.navigate(
  933                Box::new(NavigationData {
  934                    cursor_anchor: invalid_anchor,
  935                    cursor_position: invalid_point,
  936                    scroll_anchor: ScrollAnchor {
  937                        anchor: invalid_anchor,
  938                        offset: Default::default(),
  939                    },
  940                    scroll_top_row: invalid_point.row,
  941                }),
  942                window,
  943                cx,
  944            );
  945            assert_eq!(
  946                editor
  947                    .selections
  948                    .display_ranges(&editor.display_snapshot(cx)),
  949                &[editor.max_point(cx)..editor.max_point(cx)]
  950            );
  951            assert_eq!(
  952                editor.scroll_position(cx),
  953                gpui::Point::new(0., editor.max_point(cx).row().as_f64())
  954            );
  955
  956            editor
  957        })
  958    });
  959}
  960
  961#[gpui::test]
  962fn test_cancel(cx: &mut TestAppContext) {
  963    init_test(cx, |_| {});
  964
  965    let editor = cx.add_window(|window, cx| {
  966        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  967        build_editor(buffer, window, cx)
  968    });
  969
  970    _ = editor.update(cx, |editor, window, cx| {
  971        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  972        editor.update_selection(
  973            DisplayPoint::new(DisplayRow(1), 1),
  974            0,
  975            gpui::Point::<f32>::default(),
  976            window,
  977            cx,
  978        );
  979        editor.end_selection(window, cx);
  980
  981        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
  982        editor.update_selection(
  983            DisplayPoint::new(DisplayRow(0), 3),
  984            0,
  985            gpui::Point::<f32>::default(),
  986            window,
  987            cx,
  988        );
  989        editor.end_selection(window, cx);
  990        assert_eq!(
  991            display_ranges(editor, cx),
  992            [
  993                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
  994                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
  995            ]
  996        );
  997    });
  998
  999    _ = editor.update(cx, |editor, window, cx| {
 1000        editor.cancel(&Cancel, window, cx);
 1001        assert_eq!(
 1002            display_ranges(editor, cx),
 1003            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
 1004        );
 1005    });
 1006
 1007    _ = editor.update(cx, |editor, window, cx| {
 1008        editor.cancel(&Cancel, window, cx);
 1009        assert_eq!(
 1010            display_ranges(editor, cx),
 1011            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
 1012        );
 1013    });
 1014}
 1015
 1016#[gpui::test]
 1017fn test_fold_action(cx: &mut TestAppContext) {
 1018    init_test(cx, |_| {});
 1019
 1020    let editor = cx.add_window(|window, cx| {
 1021        let buffer = MultiBuffer::build_simple(
 1022            &"
 1023                impl Foo {
 1024                    // Hello!
 1025
 1026                    fn a() {
 1027                        1
 1028                    }
 1029
 1030                    fn b() {
 1031                        2
 1032                    }
 1033
 1034                    fn c() {
 1035                        3
 1036                    }
 1037                }
 1038            "
 1039            .unindent(),
 1040            cx,
 1041        );
 1042        build_editor(buffer, window, cx)
 1043    });
 1044
 1045    _ = editor.update(cx, |editor, window, cx| {
 1046        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1047            s.select_display_ranges([
 1048                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
 1049            ]);
 1050        });
 1051        editor.fold(&Fold, window, cx);
 1052        assert_eq!(
 1053            editor.display_text(cx),
 1054            "
 1055                impl Foo {
 1056                    // Hello!
 1057
 1058                    fn a() {
 1059                        1
 1060                    }
 1061
 1062                    fn b() {⋯
 1063                    }
 1064
 1065                    fn c() {⋯
 1066                    }
 1067                }
 1068            "
 1069            .unindent(),
 1070        );
 1071
 1072        editor.fold(&Fold, window, cx);
 1073        assert_eq!(
 1074            editor.display_text(cx),
 1075            "
 1076                impl Foo {⋯
 1077                }
 1078            "
 1079            .unindent(),
 1080        );
 1081
 1082        editor.unfold_lines(&UnfoldLines, window, cx);
 1083        assert_eq!(
 1084            editor.display_text(cx),
 1085            "
 1086                impl Foo {
 1087                    // Hello!
 1088
 1089                    fn a() {
 1090                        1
 1091                    }
 1092
 1093                    fn b() {⋯
 1094                    }
 1095
 1096                    fn c() {⋯
 1097                    }
 1098                }
 1099            "
 1100            .unindent(),
 1101        );
 1102
 1103        editor.unfold_lines(&UnfoldLines, window, cx);
 1104        assert_eq!(
 1105            editor.display_text(cx),
 1106            editor.buffer.read(cx).read(cx).text()
 1107        );
 1108    });
 1109}
 1110
 1111#[gpui::test]
 1112fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
 1113    init_test(cx, |_| {});
 1114
 1115    let editor = cx.add_window(|window, cx| {
 1116        let buffer = MultiBuffer::build_simple(
 1117            &"
 1118                class Foo:
 1119                    # Hello!
 1120
 1121                    def a():
 1122                        print(1)
 1123
 1124                    def b():
 1125                        print(2)
 1126
 1127                    def c():
 1128                        print(3)
 1129            "
 1130            .unindent(),
 1131            cx,
 1132        );
 1133        build_editor(buffer, window, cx)
 1134    });
 1135
 1136    _ = editor.update(cx, |editor, window, cx| {
 1137        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1138            s.select_display_ranges([
 1139                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
 1140            ]);
 1141        });
 1142        editor.fold(&Fold, window, cx);
 1143        assert_eq!(
 1144            editor.display_text(cx),
 1145            "
 1146                class Foo:
 1147                    # Hello!
 1148
 1149                    def a():
 1150                        print(1)
 1151
 1152                    def b():⋯
 1153
 1154                    def c():⋯
 1155            "
 1156            .unindent(),
 1157        );
 1158
 1159        editor.fold(&Fold, window, cx);
 1160        assert_eq!(
 1161            editor.display_text(cx),
 1162            "
 1163                class Foo:⋯
 1164            "
 1165            .unindent(),
 1166        );
 1167
 1168        editor.unfold_lines(&UnfoldLines, window, cx);
 1169        assert_eq!(
 1170            editor.display_text(cx),
 1171            "
 1172                class Foo:
 1173                    # Hello!
 1174
 1175                    def a():
 1176                        print(1)
 1177
 1178                    def b():⋯
 1179
 1180                    def c():⋯
 1181            "
 1182            .unindent(),
 1183        );
 1184
 1185        editor.unfold_lines(&UnfoldLines, window, cx);
 1186        assert_eq!(
 1187            editor.display_text(cx),
 1188            editor.buffer.read(cx).read(cx).text()
 1189        );
 1190    });
 1191}
 1192
 1193#[gpui::test]
 1194fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1195    init_test(cx, |_| {});
 1196
 1197    let editor = cx.add_window(|window, cx| {
 1198        let buffer = MultiBuffer::build_simple(
 1199            &"
 1200                class Foo:
 1201                    # Hello!
 1202
 1203                    def a():
 1204                        print(1)
 1205
 1206                    def b():
 1207                        print(2)
 1208
 1209
 1210                    def c():
 1211                        print(3)
 1212
 1213
 1214            "
 1215            .unindent(),
 1216            cx,
 1217        );
 1218        build_editor(buffer, window, cx)
 1219    });
 1220
 1221    _ = editor.update(cx, |editor, window, cx| {
 1222        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1223            s.select_display_ranges([
 1224                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1225            ]);
 1226        });
 1227        editor.fold(&Fold, window, cx);
 1228        assert_eq!(
 1229            editor.display_text(cx),
 1230            "
 1231                class Foo:
 1232                    # Hello!
 1233
 1234                    def a():
 1235                        print(1)
 1236
 1237                    def b():⋯
 1238
 1239
 1240                    def c():⋯
 1241
 1242
 1243            "
 1244            .unindent(),
 1245        );
 1246
 1247        editor.fold(&Fold, window, cx);
 1248        assert_eq!(
 1249            editor.display_text(cx),
 1250            "
 1251                class Foo:⋯
 1252
 1253
 1254            "
 1255            .unindent(),
 1256        );
 1257
 1258        editor.unfold_lines(&UnfoldLines, window, cx);
 1259        assert_eq!(
 1260            editor.display_text(cx),
 1261            "
 1262                class Foo:
 1263                    # Hello!
 1264
 1265                    def a():
 1266                        print(1)
 1267
 1268                    def b():⋯
 1269
 1270
 1271                    def c():⋯
 1272
 1273
 1274            "
 1275            .unindent(),
 1276        );
 1277
 1278        editor.unfold_lines(&UnfoldLines, window, cx);
 1279        assert_eq!(
 1280            editor.display_text(cx),
 1281            editor.buffer.read(cx).read(cx).text()
 1282        );
 1283    });
 1284}
 1285
 1286#[gpui::test]
 1287fn test_fold_at_level(cx: &mut TestAppContext) {
 1288    init_test(cx, |_| {});
 1289
 1290    let editor = cx.add_window(|window, cx| {
 1291        let buffer = MultiBuffer::build_simple(
 1292            &"
 1293                class Foo:
 1294                    # Hello!
 1295
 1296                    def a():
 1297                        print(1)
 1298
 1299                    def b():
 1300                        print(2)
 1301
 1302
 1303                class Bar:
 1304                    # World!
 1305
 1306                    def a():
 1307                        print(1)
 1308
 1309                    def b():
 1310                        print(2)
 1311
 1312
 1313            "
 1314            .unindent(),
 1315            cx,
 1316        );
 1317        build_editor(buffer, window, cx)
 1318    });
 1319
 1320    _ = editor.update(cx, |editor, window, cx| {
 1321        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1322        assert_eq!(
 1323            editor.display_text(cx),
 1324            "
 1325                class Foo:
 1326                    # Hello!
 1327
 1328                    def a():⋯
 1329
 1330                    def b():⋯
 1331
 1332
 1333                class Bar:
 1334                    # World!
 1335
 1336                    def a():⋯
 1337
 1338                    def b():⋯
 1339
 1340
 1341            "
 1342            .unindent(),
 1343        );
 1344
 1345        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1346        assert_eq!(
 1347            editor.display_text(cx),
 1348            "
 1349                class Foo:⋯
 1350
 1351
 1352                class Bar:⋯
 1353
 1354
 1355            "
 1356            .unindent(),
 1357        );
 1358
 1359        editor.unfold_all(&UnfoldAll, window, cx);
 1360        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1361        assert_eq!(
 1362            editor.display_text(cx),
 1363            "
 1364                class Foo:
 1365                    # Hello!
 1366
 1367                    def a():
 1368                        print(1)
 1369
 1370                    def b():
 1371                        print(2)
 1372
 1373
 1374                class Bar:
 1375                    # World!
 1376
 1377                    def a():
 1378                        print(1)
 1379
 1380                    def b():
 1381                        print(2)
 1382
 1383
 1384            "
 1385            .unindent(),
 1386        );
 1387
 1388        assert_eq!(
 1389            editor.display_text(cx),
 1390            editor.buffer.read(cx).read(cx).text()
 1391        );
 1392        let (_, positions) = marked_text_ranges(
 1393            &"
 1394                       class Foo:
 1395                           # Hello!
 1396
 1397                           def a():
 1398                              print(1)
 1399
 1400                           def b():
 1401                               p«riˇ»nt(2)
 1402
 1403
 1404                       class Bar:
 1405                           # World!
 1406
 1407                           def a():
 1408                               «ˇprint(1)
 1409
 1410                           def b():
 1411                               print(2)»
 1412
 1413
 1414                   "
 1415            .unindent(),
 1416            true,
 1417        );
 1418
 1419        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 1420            s.select_ranges(positions)
 1421        });
 1422
 1423        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1424        assert_eq!(
 1425            editor.display_text(cx),
 1426            "
 1427                class Foo:
 1428                    # Hello!
 1429
 1430                    def a():⋯
 1431
 1432                    def b():
 1433                        print(2)
 1434
 1435
 1436                class Bar:
 1437                    # World!
 1438
 1439                    def a():
 1440                        print(1)
 1441
 1442                    def b():
 1443                        print(2)
 1444
 1445
 1446            "
 1447            .unindent(),
 1448        );
 1449    });
 1450}
 1451
 1452#[gpui::test]
 1453fn test_move_cursor(cx: &mut TestAppContext) {
 1454    init_test(cx, |_| {});
 1455
 1456    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1457    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1458
 1459    buffer.update(cx, |buffer, cx| {
 1460        buffer.edit(
 1461            vec![
 1462                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1463                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1464            ],
 1465            None,
 1466            cx,
 1467        );
 1468    });
 1469    _ = editor.update(cx, |editor, window, cx| {
 1470        assert_eq!(
 1471            display_ranges(editor, cx),
 1472            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1473        );
 1474
 1475        editor.move_down(&MoveDown, window, cx);
 1476        assert_eq!(
 1477            display_ranges(editor, cx),
 1478            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1479        );
 1480
 1481        editor.move_right(&MoveRight, window, cx);
 1482        assert_eq!(
 1483            display_ranges(editor, cx),
 1484            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1485        );
 1486
 1487        editor.move_left(&MoveLeft, window, cx);
 1488        assert_eq!(
 1489            display_ranges(editor, cx),
 1490            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1491        );
 1492
 1493        editor.move_up(&MoveUp, window, cx);
 1494        assert_eq!(
 1495            display_ranges(editor, cx),
 1496            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1497        );
 1498
 1499        editor.move_to_end(&MoveToEnd, window, cx);
 1500        assert_eq!(
 1501            display_ranges(editor, cx),
 1502            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1503        );
 1504
 1505        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1506        assert_eq!(
 1507            display_ranges(editor, cx),
 1508            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1509        );
 1510
 1511        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1512            s.select_display_ranges([
 1513                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1514            ]);
 1515        });
 1516        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1517        assert_eq!(
 1518            display_ranges(editor, cx),
 1519            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1520        );
 1521
 1522        editor.select_to_end(&SelectToEnd, window, cx);
 1523        assert_eq!(
 1524            display_ranges(editor, cx),
 1525            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1526        );
 1527    });
 1528}
 1529
 1530#[gpui::test]
 1531fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1532    init_test(cx, |_| {});
 1533
 1534    let editor = cx.add_window(|window, cx| {
 1535        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1536        build_editor(buffer, window, cx)
 1537    });
 1538
 1539    assert_eq!('🟥'.len_utf8(), 4);
 1540    assert_eq!('α'.len_utf8(), 2);
 1541
 1542    _ = editor.update(cx, |editor, window, cx| {
 1543        editor.fold_creases(
 1544            vec![
 1545                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1546                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1547                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1548            ],
 1549            true,
 1550            window,
 1551            cx,
 1552        );
 1553        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1554
 1555        editor.move_right(&MoveRight, window, cx);
 1556        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥".len())]);
 1557        editor.move_right(&MoveRight, window, cx);
 1558        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥🟧".len())]);
 1559        editor.move_right(&MoveRight, window, cx);
 1560        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥🟧⋯".len())]);
 1561
 1562        editor.move_down(&MoveDown, window, cx);
 1563        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯e".len())]);
 1564        editor.move_left(&MoveLeft, window, cx);
 1565        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯".len())]);
 1566        editor.move_left(&MoveLeft, window, cx);
 1567        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab".len())]);
 1568        editor.move_left(&MoveLeft, window, cx);
 1569        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "a".len())]);
 1570
 1571        editor.move_down(&MoveDown, window, cx);
 1572        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "α".len())]);
 1573        editor.move_right(&MoveRight, window, cx);
 1574        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ".len())]);
 1575        editor.move_right(&MoveRight, window, cx);
 1576        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ⋯".len())]);
 1577        editor.move_right(&MoveRight, window, cx);
 1578        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ⋯ε".len())]);
 1579
 1580        editor.move_up(&MoveUp, window, cx);
 1581        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯e".len())]);
 1582        editor.move_down(&MoveDown, window, cx);
 1583        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ⋯ε".len())]);
 1584        editor.move_up(&MoveUp, window, cx);
 1585        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯e".len())]);
 1586
 1587        editor.move_up(&MoveUp, window, cx);
 1588        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥🟧".len())]);
 1589        editor.move_left(&MoveLeft, window, cx);
 1590        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥".len())]);
 1591        editor.move_left(&MoveLeft, window, cx);
 1592        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "".len())]);
 1593    });
 1594}
 1595
 1596#[gpui::test]
 1597fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1598    init_test(cx, |_| {});
 1599
 1600    let editor = cx.add_window(|window, cx| {
 1601        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1602        build_editor(buffer, window, cx)
 1603    });
 1604    _ = editor.update(cx, |editor, window, cx| {
 1605        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1606            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1607        });
 1608
 1609        // moving above start of document should move selection to start of document,
 1610        // but the next move down should still be at the original goal_x
 1611        editor.move_up(&MoveUp, window, cx);
 1612        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "".len())]);
 1613
 1614        editor.move_down(&MoveDown, window, cx);
 1615        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "abcd".len())]);
 1616
 1617        editor.move_down(&MoveDown, window, cx);
 1618        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβγ".len())]);
 1619
 1620        editor.move_down(&MoveDown, window, cx);
 1621        assert_eq!(display_ranges(editor, cx), &[empty_range(3, "abcd".len())]);
 1622
 1623        editor.move_down(&MoveDown, window, cx);
 1624        assert_eq!(display_ranges(editor, cx), &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]);
 1625
 1626        // moving past end of document should not change goal_x
 1627        editor.move_down(&MoveDown, window, cx);
 1628        assert_eq!(display_ranges(editor, cx), &[empty_range(5, "".len())]);
 1629
 1630        editor.move_down(&MoveDown, window, cx);
 1631        assert_eq!(display_ranges(editor, cx), &[empty_range(5, "".len())]);
 1632
 1633        editor.move_up(&MoveUp, window, cx);
 1634        assert_eq!(display_ranges(editor, cx), &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]);
 1635
 1636        editor.move_up(&MoveUp, window, cx);
 1637        assert_eq!(display_ranges(editor, cx), &[empty_range(3, "abcd".len())]);
 1638
 1639        editor.move_up(&MoveUp, window, cx);
 1640        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβγ".len())]);
 1641    });
 1642}
 1643
 1644#[gpui::test]
 1645fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1646    init_test(cx, |_| {});
 1647    let move_to_beg = MoveToBeginningOfLine {
 1648        stop_at_soft_wraps: true,
 1649        stop_at_indent: true,
 1650    };
 1651
 1652    let delete_to_beg = DeleteToBeginningOfLine {
 1653        stop_at_indent: false,
 1654    };
 1655
 1656    let move_to_end = MoveToEndOfLine {
 1657        stop_at_soft_wraps: true,
 1658    };
 1659
 1660    let editor = cx.add_window(|window, cx| {
 1661        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1662        build_editor(buffer, window, cx)
 1663    });
 1664    _ = editor.update(cx, |editor, window, cx| {
 1665        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1666            s.select_display_ranges([
 1667                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1668                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1669            ]);
 1670        });
 1671    });
 1672
 1673    _ = editor.update(cx, |editor, window, cx| {
 1674        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1675        assert_eq!(
 1676            display_ranges(editor, cx),
 1677            &[
 1678                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1679                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1680            ]
 1681        );
 1682    });
 1683
 1684    _ = editor.update(cx, |editor, window, cx| {
 1685        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1686        assert_eq!(
 1687            display_ranges(editor, cx),
 1688            &[
 1689                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1690                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1691            ]
 1692        );
 1693    });
 1694
 1695    _ = editor.update(cx, |editor, window, cx| {
 1696        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1697        assert_eq!(
 1698            display_ranges(editor, cx),
 1699            &[
 1700                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1701                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1702            ]
 1703        );
 1704    });
 1705
 1706    _ = editor.update(cx, |editor, window, cx| {
 1707        editor.move_to_end_of_line(&move_to_end, window, cx);
 1708        assert_eq!(
 1709            display_ranges(editor, cx),
 1710            &[
 1711                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1712                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1713            ]
 1714        );
 1715    });
 1716
 1717    // Moving to the end of line again is a no-op.
 1718    _ = editor.update(cx, |editor, window, cx| {
 1719        editor.move_to_end_of_line(&move_to_end, window, cx);
 1720        assert_eq!(
 1721            display_ranges(editor, cx),
 1722            &[
 1723                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1724                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1725            ]
 1726        );
 1727    });
 1728
 1729    _ = editor.update(cx, |editor, window, cx| {
 1730        editor.move_left(&MoveLeft, window, cx);
 1731        editor.select_to_beginning_of_line(
 1732            &SelectToBeginningOfLine {
 1733                stop_at_soft_wraps: true,
 1734                stop_at_indent: true,
 1735            },
 1736            window,
 1737            cx,
 1738        );
 1739        assert_eq!(
 1740            display_ranges(editor, cx),
 1741            &[
 1742                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1743                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1744            ]
 1745        );
 1746    });
 1747
 1748    _ = editor.update(cx, |editor, window, cx| {
 1749        editor.select_to_beginning_of_line(
 1750            &SelectToBeginningOfLine {
 1751                stop_at_soft_wraps: true,
 1752                stop_at_indent: true,
 1753            },
 1754            window,
 1755            cx,
 1756        );
 1757        assert_eq!(
 1758            display_ranges(editor, cx),
 1759            &[
 1760                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1761                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1762            ]
 1763        );
 1764    });
 1765
 1766    _ = editor.update(cx, |editor, window, cx| {
 1767        editor.select_to_beginning_of_line(
 1768            &SelectToBeginningOfLine {
 1769                stop_at_soft_wraps: true,
 1770                stop_at_indent: true,
 1771            },
 1772            window,
 1773            cx,
 1774        );
 1775        assert_eq!(
 1776            display_ranges(editor, cx),
 1777            &[
 1778                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1779                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1780            ]
 1781        );
 1782    });
 1783
 1784    _ = editor.update(cx, |editor, window, cx| {
 1785        editor.select_to_end_of_line(
 1786            &SelectToEndOfLine {
 1787                stop_at_soft_wraps: true,
 1788            },
 1789            window,
 1790            cx,
 1791        );
 1792        assert_eq!(
 1793            display_ranges(editor, cx),
 1794            &[
 1795                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1796                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1797            ]
 1798        );
 1799    });
 1800
 1801    _ = editor.update(cx, |editor, window, cx| {
 1802        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1803        assert_eq!(editor.display_text(cx), "ab\n  de");
 1804        assert_eq!(
 1805            display_ranges(editor, cx),
 1806            &[
 1807                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1808                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1809            ]
 1810        );
 1811    });
 1812
 1813    _ = editor.update(cx, |editor, window, cx| {
 1814        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1815        assert_eq!(editor.display_text(cx), "\n");
 1816        assert_eq!(
 1817            display_ranges(editor, cx),
 1818            &[
 1819                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1820                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1821            ]
 1822        );
 1823    });
 1824}
 1825
 1826#[gpui::test]
 1827fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1828    init_test(cx, |_| {});
 1829    let move_to_beg = MoveToBeginningOfLine {
 1830        stop_at_soft_wraps: false,
 1831        stop_at_indent: false,
 1832    };
 1833
 1834    let move_to_end = MoveToEndOfLine {
 1835        stop_at_soft_wraps: false,
 1836    };
 1837
 1838    let editor = cx.add_window(|window, cx| {
 1839        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1840        build_editor(buffer, window, cx)
 1841    });
 1842
 1843    _ = editor.update(cx, |editor, window, cx| {
 1844        editor.set_wrap_width(Some(140.0.into()), cx);
 1845
 1846        // We expect the following lines after wrapping
 1847        // ```
 1848        // thequickbrownfox
 1849        // jumpedoverthelazydo
 1850        // gs
 1851        // ```
 1852        // The final `gs` was soft-wrapped onto a new line.
 1853        assert_eq!(
 1854            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1855            editor.display_text(cx),
 1856        );
 1857
 1858        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1859        // Start the cursor at the `k` on the first line
 1860        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1861            s.select_display_ranges([
 1862                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1863            ]);
 1864        });
 1865
 1866        // Moving to the beginning of the line should put us at the beginning of the line.
 1867        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1868        assert_eq!(
 1869            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1870            display_ranges(editor, cx)
 1871        );
 1872
 1873        // Moving to the end of the line should put us at the end of the line.
 1874        editor.move_to_end_of_line(&move_to_end, window, cx);
 1875        assert_eq!(
 1876            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1877            display_ranges(editor, cx)
 1878        );
 1879
 1880        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1881        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1882        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1883            s.select_display_ranges([
 1884                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1885            ]);
 1886        });
 1887
 1888        // Moving to the beginning of the line should put us at the start of the second line of
 1889        // display text, i.e., the `j`.
 1890        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1891        assert_eq!(
 1892            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1893            display_ranges(editor, cx)
 1894        );
 1895
 1896        // Moving to the beginning of the line again should be a no-op.
 1897        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1898        assert_eq!(
 1899            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1900            display_ranges(editor, cx)
 1901        );
 1902
 1903        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1904        // next display line.
 1905        editor.move_to_end_of_line(&move_to_end, window, cx);
 1906        assert_eq!(
 1907            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1908            display_ranges(editor, cx)
 1909        );
 1910
 1911        // Moving to the end of the line again should be a no-op.
 1912        editor.move_to_end_of_line(&move_to_end, window, cx);
 1913        assert_eq!(
 1914            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1915            display_ranges(editor, cx)
 1916        );
 1917    });
 1918}
 1919
 1920#[gpui::test]
 1921fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1922    init_test(cx, |_| {});
 1923
 1924    let move_to_beg = MoveToBeginningOfLine {
 1925        stop_at_soft_wraps: true,
 1926        stop_at_indent: true,
 1927    };
 1928
 1929    let select_to_beg = SelectToBeginningOfLine {
 1930        stop_at_soft_wraps: true,
 1931        stop_at_indent: true,
 1932    };
 1933
 1934    let delete_to_beg = DeleteToBeginningOfLine {
 1935        stop_at_indent: true,
 1936    };
 1937
 1938    let move_to_end = MoveToEndOfLine {
 1939        stop_at_soft_wraps: false,
 1940    };
 1941
 1942    let editor = cx.add_window(|window, cx| {
 1943        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1944        build_editor(buffer, window, cx)
 1945    });
 1946
 1947    _ = editor.update(cx, |editor, window, cx| {
 1948        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1949            s.select_display_ranges([
 1950                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1951                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1952            ]);
 1953        });
 1954
 1955        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 1956        // and the second cursor at the first non-whitespace character in the line.
 1957        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1958        assert_eq!(
 1959            display_ranges(editor, cx),
 1960            &[
 1961                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1962                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1963            ]
 1964        );
 1965
 1966        // Moving to the beginning of the line again should be a no-op for the first cursor,
 1967        // and should move the second cursor to the beginning of the line.
 1968        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1969        assert_eq!(
 1970            display_ranges(editor, cx),
 1971            &[
 1972                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1973                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1974            ]
 1975        );
 1976
 1977        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 1978        // and should move the second cursor back to the first non-whitespace character in the line.
 1979        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1980        assert_eq!(
 1981            display_ranges(editor, cx),
 1982            &[
 1983                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1984                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1985            ]
 1986        );
 1987
 1988        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 1989        // and to the first non-whitespace character in the line for the second cursor.
 1990        editor.move_to_end_of_line(&move_to_end, window, cx);
 1991        editor.move_left(&MoveLeft, window, cx);
 1992        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1993        assert_eq!(
 1994            display_ranges(editor, cx),
 1995            &[
 1996                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1997                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1998            ]
 1999        );
 2000
 2001        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 2002        // and should select to the beginning of the line for the second cursor.
 2003        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 2004        assert_eq!(
 2005            display_ranges(editor, cx),
 2006            &[
 2007                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 2008                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 2009            ]
 2010        );
 2011
 2012        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 2013        // and should delete to the first non-whitespace character in the line for the second cursor.
 2014        editor.move_to_end_of_line(&move_to_end, window, cx);
 2015        editor.move_left(&MoveLeft, window, cx);
 2016        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 2017        assert_eq!(editor.text(cx), "c\n  f");
 2018    });
 2019}
 2020
 2021#[gpui::test]
 2022fn test_beginning_of_line_with_cursor_between_line_start_and_indent(cx: &mut TestAppContext) {
 2023    init_test(cx, |_| {});
 2024
 2025    let move_to_beg = MoveToBeginningOfLine {
 2026        stop_at_soft_wraps: true,
 2027        stop_at_indent: true,
 2028    };
 2029
 2030    let editor = cx.add_window(|window, cx| {
 2031        let buffer = MultiBuffer::build_simple("    hello\nworld", cx);
 2032        build_editor(buffer, window, cx)
 2033    });
 2034
 2035    _ = editor.update(cx, |editor, window, cx| {
 2036        // test cursor between line_start and indent_start
 2037        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2038            s.select_display_ranges([
 2039                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3)
 2040            ]);
 2041        });
 2042
 2043        // cursor should move to line_start
 2044        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2045        assert_eq!(
 2046            display_ranges(editor, cx),
 2047            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 2048        );
 2049
 2050        // cursor should move to indent_start
 2051        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2052        assert_eq!(
 2053            display_ranges(editor, cx),
 2054            &[DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 4)]
 2055        );
 2056
 2057        // cursor should move to back to line_start
 2058        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2059        assert_eq!(
 2060            display_ranges(editor, cx),
 2061            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 2062        );
 2063    });
 2064}
 2065
 2066#[gpui::test]
 2067fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 2068    init_test(cx, |_| {});
 2069
 2070    let editor = cx.add_window(|window, cx| {
 2071        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 2072        build_editor(buffer, window, cx)
 2073    });
 2074    _ = editor.update(cx, |editor, window, cx| {
 2075        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2076            s.select_display_ranges([
 2077                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 2078                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 2079            ])
 2080        });
 2081        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2082        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 2083
 2084        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2085        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 2086
 2087        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2088        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 2089
 2090        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2091        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 2092
 2093        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2094        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 2095
 2096        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2097        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 2098
 2099        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2100        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 2101
 2102        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2103        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 2104
 2105        editor.move_right(&MoveRight, window, cx);
 2106        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2107        assert_selection_ranges(
 2108            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 2109            editor,
 2110            cx,
 2111        );
 2112
 2113        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2114        assert_selection_ranges(
 2115            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 2116            editor,
 2117            cx,
 2118        );
 2119
 2120        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 2121        assert_selection_ranges(
 2122            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 2123            editor,
 2124            cx,
 2125        );
 2126    });
 2127}
 2128
 2129#[gpui::test]
 2130fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 2131    init_test(cx, |_| {});
 2132
 2133    let editor = cx.add_window(|window, cx| {
 2134        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 2135        build_editor(buffer, window, cx)
 2136    });
 2137
 2138    _ = editor.update(cx, |editor, window, cx| {
 2139        editor.set_wrap_width(Some(140.0.into()), cx);
 2140        assert_eq!(
 2141            editor.display_text(cx),
 2142            "use one::{\n    two::three::\n    four::five\n};"
 2143        );
 2144
 2145        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2146            s.select_display_ranges([
 2147                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 2148            ]);
 2149        });
 2150
 2151        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2152        assert_eq!(
 2153            display_ranges(editor, cx),
 2154            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 2155        );
 2156
 2157        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2158        assert_eq!(
 2159            display_ranges(editor, cx),
 2160            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2161        );
 2162
 2163        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2164        assert_eq!(
 2165            display_ranges(editor, cx),
 2166            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2167        );
 2168
 2169        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2170        assert_eq!(
 2171            display_ranges(editor, cx),
 2172            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2173        );
 2174
 2175        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2176        assert_eq!(
 2177            display_ranges(editor, cx),
 2178            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2179        );
 2180
 2181        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2182        assert_eq!(
 2183            display_ranges(editor, cx),
 2184            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2185        );
 2186    });
 2187}
 2188
 2189#[gpui::test]
 2190async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2191    init_test(cx, |_| {});
 2192    let mut cx = EditorTestContext::new(cx).await;
 2193
 2194    let line_height = cx.editor(|editor, window, _| {
 2195        editor
 2196            .style()
 2197            .unwrap()
 2198            .text
 2199            .line_height_in_pixels(window.rem_size())
 2200    });
 2201    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2202
 2203    cx.set_state(
 2204        &r#"ˇone
 2205        two
 2206
 2207        three
 2208        fourˇ
 2209        five
 2210
 2211        six"#
 2212            .unindent(),
 2213    );
 2214
 2215    cx.update_editor(|editor, window, cx| {
 2216        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2217    });
 2218    cx.assert_editor_state(
 2219        &r#"one
 2220        two
 2221        ˇ
 2222        three
 2223        four
 2224        five
 2225        ˇ
 2226        six"#
 2227            .unindent(),
 2228    );
 2229
 2230    cx.update_editor(|editor, window, cx| {
 2231        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2232    });
 2233    cx.assert_editor_state(
 2234        &r#"one
 2235        two
 2236
 2237        three
 2238        four
 2239        five
 2240        ˇ
 2241        sixˇ"#
 2242            .unindent(),
 2243    );
 2244
 2245    cx.update_editor(|editor, window, cx| {
 2246        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2247    });
 2248    cx.assert_editor_state(
 2249        &r#"one
 2250        two
 2251
 2252        three
 2253        four
 2254        five
 2255
 2256        sixˇ"#
 2257            .unindent(),
 2258    );
 2259
 2260    cx.update_editor(|editor, window, cx| {
 2261        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2262    });
 2263    cx.assert_editor_state(
 2264        &r#"one
 2265        two
 2266
 2267        three
 2268        four
 2269        five
 2270        ˇ
 2271        six"#
 2272            .unindent(),
 2273    );
 2274
 2275    cx.update_editor(|editor, window, cx| {
 2276        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2277    });
 2278    cx.assert_editor_state(
 2279        &r#"one
 2280        two
 2281        ˇ
 2282        three
 2283        four
 2284        five
 2285
 2286        six"#
 2287            .unindent(),
 2288    );
 2289
 2290    cx.update_editor(|editor, window, cx| {
 2291        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2292    });
 2293    cx.assert_editor_state(
 2294        &r#"ˇone
 2295        two
 2296
 2297        three
 2298        four
 2299        five
 2300
 2301        six"#
 2302            .unindent(),
 2303    );
 2304}
 2305
 2306#[gpui::test]
 2307async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2308    init_test(cx, |_| {});
 2309    let mut cx = EditorTestContext::new(cx).await;
 2310    let line_height = cx.editor(|editor, window, _| {
 2311        editor
 2312            .style()
 2313            .unwrap()
 2314            .text
 2315            .line_height_in_pixels(window.rem_size())
 2316    });
 2317    let window = cx.window;
 2318    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2319
 2320    cx.set_state(
 2321        r#"ˇone
 2322        two
 2323        three
 2324        four
 2325        five
 2326        six
 2327        seven
 2328        eight
 2329        nine
 2330        ten
 2331        "#,
 2332    );
 2333
 2334    cx.update_editor(|editor, window, cx| {
 2335        assert_eq!(
 2336            editor.snapshot(window, cx).scroll_position(),
 2337            gpui::Point::new(0., 0.)
 2338        );
 2339        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2340        assert_eq!(
 2341            editor.snapshot(window, cx).scroll_position(),
 2342            gpui::Point::new(0., 3.)
 2343        );
 2344        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2345        assert_eq!(
 2346            editor.snapshot(window, cx).scroll_position(),
 2347            gpui::Point::new(0., 6.)
 2348        );
 2349        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2350        assert_eq!(
 2351            editor.snapshot(window, cx).scroll_position(),
 2352            gpui::Point::new(0., 3.)
 2353        );
 2354
 2355        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2356        assert_eq!(
 2357            editor.snapshot(window, cx).scroll_position(),
 2358            gpui::Point::new(0., 1.)
 2359        );
 2360        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2361        assert_eq!(
 2362            editor.snapshot(window, cx).scroll_position(),
 2363            gpui::Point::new(0., 3.)
 2364        );
 2365    });
 2366}
 2367
 2368#[gpui::test]
 2369async fn test_autoscroll(cx: &mut TestAppContext) {
 2370    init_test(cx, |_| {});
 2371    let mut cx = EditorTestContext::new(cx).await;
 2372
 2373    let line_height = cx.update_editor(|editor, window, cx| {
 2374        editor.set_vertical_scroll_margin(2, cx);
 2375        editor
 2376            .style()
 2377            .unwrap()
 2378            .text
 2379            .line_height_in_pixels(window.rem_size())
 2380    });
 2381    let window = cx.window;
 2382    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2383
 2384    cx.set_state(
 2385        r#"ˇone
 2386            two
 2387            three
 2388            four
 2389            five
 2390            six
 2391            seven
 2392            eight
 2393            nine
 2394            ten
 2395        "#,
 2396    );
 2397    cx.update_editor(|editor, window, cx| {
 2398        assert_eq!(
 2399            editor.snapshot(window, cx).scroll_position(),
 2400            gpui::Point::new(0., 0.0)
 2401        );
 2402    });
 2403
 2404    // Add a cursor below the visible area. Since both cursors cannot fit
 2405    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2406    // allows the vertical scroll margin below that cursor.
 2407    cx.update_editor(|editor, window, cx| {
 2408        editor.change_selections(Default::default(), window, cx, |selections| {
 2409            selections.select_ranges([
 2410                Point::new(0, 0)..Point::new(0, 0),
 2411                Point::new(6, 0)..Point::new(6, 0),
 2412            ]);
 2413        })
 2414    });
 2415    cx.update_editor(|editor, window, cx| {
 2416        assert_eq!(
 2417            editor.snapshot(window, cx).scroll_position(),
 2418            gpui::Point::new(0., 3.0)
 2419        );
 2420    });
 2421
 2422    // Move down. The editor cursor scrolls down to track the newest cursor.
 2423    cx.update_editor(|editor, window, cx| {
 2424        editor.move_down(&Default::default(), window, cx);
 2425    });
 2426    cx.update_editor(|editor, window, cx| {
 2427        assert_eq!(
 2428            editor.snapshot(window, cx).scroll_position(),
 2429            gpui::Point::new(0., 4.0)
 2430        );
 2431    });
 2432
 2433    // Add a cursor above the visible area. Since both cursors fit on screen,
 2434    // the editor scrolls to show both.
 2435    cx.update_editor(|editor, window, cx| {
 2436        editor.change_selections(Default::default(), window, cx, |selections| {
 2437            selections.select_ranges([
 2438                Point::new(1, 0)..Point::new(1, 0),
 2439                Point::new(6, 0)..Point::new(6, 0),
 2440            ]);
 2441        })
 2442    });
 2443    cx.update_editor(|editor, window, cx| {
 2444        assert_eq!(
 2445            editor.snapshot(window, cx).scroll_position(),
 2446            gpui::Point::new(0., 1.0)
 2447        );
 2448    });
 2449}
 2450
 2451#[gpui::test]
 2452async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2453    init_test(cx, |_| {});
 2454    let mut cx = EditorTestContext::new(cx).await;
 2455
 2456    let line_height = cx.editor(|editor, window, _cx| {
 2457        editor
 2458            .style()
 2459            .unwrap()
 2460            .text
 2461            .line_height_in_pixels(window.rem_size())
 2462    });
 2463    let window = cx.window;
 2464    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2465    cx.set_state(
 2466        &r#"
 2467        ˇone
 2468        two
 2469        threeˇ
 2470        four
 2471        five
 2472        six
 2473        seven
 2474        eight
 2475        nine
 2476        ten
 2477        "#
 2478        .unindent(),
 2479    );
 2480
 2481    cx.update_editor(|editor, window, cx| {
 2482        editor.move_page_down(&MovePageDown::default(), window, cx)
 2483    });
 2484    cx.assert_editor_state(
 2485        &r#"
 2486        one
 2487        two
 2488        three
 2489        ˇfour
 2490        five
 2491        sixˇ
 2492        seven
 2493        eight
 2494        nine
 2495        ten
 2496        "#
 2497        .unindent(),
 2498    );
 2499
 2500    cx.update_editor(|editor, window, cx| {
 2501        editor.move_page_down(&MovePageDown::default(), window, cx)
 2502    });
 2503    cx.assert_editor_state(
 2504        &r#"
 2505        one
 2506        two
 2507        three
 2508        four
 2509        five
 2510        six
 2511        ˇseven
 2512        eight
 2513        nineˇ
 2514        ten
 2515        "#
 2516        .unindent(),
 2517    );
 2518
 2519    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2520    cx.assert_editor_state(
 2521        &r#"
 2522        one
 2523        two
 2524        three
 2525        ˇfour
 2526        five
 2527        sixˇ
 2528        seven
 2529        eight
 2530        nine
 2531        ten
 2532        "#
 2533        .unindent(),
 2534    );
 2535
 2536    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2537    cx.assert_editor_state(
 2538        &r#"
 2539        ˇone
 2540        two
 2541        threeˇ
 2542        four
 2543        five
 2544        six
 2545        seven
 2546        eight
 2547        nine
 2548        ten
 2549        "#
 2550        .unindent(),
 2551    );
 2552
 2553    // Test select collapsing
 2554    cx.update_editor(|editor, window, cx| {
 2555        editor.move_page_down(&MovePageDown::default(), window, cx);
 2556        editor.move_page_down(&MovePageDown::default(), window, cx);
 2557        editor.move_page_down(&MovePageDown::default(), window, cx);
 2558    });
 2559    cx.assert_editor_state(
 2560        &r#"
 2561        one
 2562        two
 2563        three
 2564        four
 2565        five
 2566        six
 2567        seven
 2568        eight
 2569        nine
 2570        ˇten
 2571        ˇ"#
 2572        .unindent(),
 2573    );
 2574}
 2575
 2576#[gpui::test]
 2577async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2578    init_test(cx, |_| {});
 2579    let mut cx = EditorTestContext::new(cx).await;
 2580    cx.set_state("one «two threeˇ» four");
 2581    cx.update_editor(|editor, window, cx| {
 2582        editor.delete_to_beginning_of_line(
 2583            &DeleteToBeginningOfLine {
 2584                stop_at_indent: false,
 2585            },
 2586            window,
 2587            cx,
 2588        );
 2589        assert_eq!(editor.text(cx), " four");
 2590    });
 2591}
 2592
 2593#[gpui::test]
 2594async fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2595    init_test(cx, |_| {});
 2596
 2597    let mut cx = EditorTestContext::new(cx).await;
 2598
 2599    // For an empty selection, the preceding word fragment is deleted.
 2600    // For non-empty selections, only selected characters are deleted.
 2601    cx.set_state("onˇe two t«hreˇ»e four");
 2602    cx.update_editor(|editor, window, cx| {
 2603        editor.delete_to_previous_word_start(
 2604            &DeleteToPreviousWordStart {
 2605                ignore_newlines: false,
 2606                ignore_brackets: false,
 2607            },
 2608            window,
 2609            cx,
 2610        );
 2611    });
 2612    cx.assert_editor_state("ˇe two tˇe four");
 2613
 2614    cx.set_state("e tˇwo te «fˇ»our");
 2615    cx.update_editor(|editor, window, cx| {
 2616        editor.delete_to_next_word_end(
 2617            &DeleteToNextWordEnd {
 2618                ignore_newlines: false,
 2619                ignore_brackets: false,
 2620            },
 2621            window,
 2622            cx,
 2623        );
 2624    });
 2625    cx.assert_editor_state("e tˇ te ˇour");
 2626}
 2627
 2628#[gpui::test]
 2629async fn test_delete_whitespaces(cx: &mut TestAppContext) {
 2630    init_test(cx, |_| {});
 2631
 2632    let mut cx = EditorTestContext::new(cx).await;
 2633
 2634    cx.set_state("here is some text    ˇwith a space");
 2635    cx.update_editor(|editor, window, cx| {
 2636        editor.delete_to_previous_word_start(
 2637            &DeleteToPreviousWordStart {
 2638                ignore_newlines: false,
 2639                ignore_brackets: true,
 2640            },
 2641            window,
 2642            cx,
 2643        );
 2644    });
 2645    // Continuous whitespace sequences are removed entirely, words behind them are not affected by the deletion action.
 2646    cx.assert_editor_state("here is some textˇwith a space");
 2647
 2648    cx.set_state("here is some text    ˇwith a space");
 2649    cx.update_editor(|editor, window, cx| {
 2650        editor.delete_to_previous_word_start(
 2651            &DeleteToPreviousWordStart {
 2652                ignore_newlines: false,
 2653                ignore_brackets: false,
 2654            },
 2655            window,
 2656            cx,
 2657        );
 2658    });
 2659    cx.assert_editor_state("here is some textˇwith a space");
 2660
 2661    cx.set_state("here is some textˇ    with a space");
 2662    cx.update_editor(|editor, window, cx| {
 2663        editor.delete_to_next_word_end(
 2664            &DeleteToNextWordEnd {
 2665                ignore_newlines: false,
 2666                ignore_brackets: true,
 2667            },
 2668            window,
 2669            cx,
 2670        );
 2671    });
 2672    // Same happens in the other direction.
 2673    cx.assert_editor_state("here is some textˇwith a space");
 2674
 2675    cx.set_state("here is some textˇ    with a space");
 2676    cx.update_editor(|editor, window, cx| {
 2677        editor.delete_to_next_word_end(
 2678            &DeleteToNextWordEnd {
 2679                ignore_newlines: false,
 2680                ignore_brackets: false,
 2681            },
 2682            window,
 2683            cx,
 2684        );
 2685    });
 2686    cx.assert_editor_state("here is some textˇwith a space");
 2687
 2688    cx.set_state("here is some textˇ    with a space");
 2689    cx.update_editor(|editor, window, cx| {
 2690        editor.delete_to_next_word_end(
 2691            &DeleteToNextWordEnd {
 2692                ignore_newlines: true,
 2693                ignore_brackets: false,
 2694            },
 2695            window,
 2696            cx,
 2697        );
 2698    });
 2699    cx.assert_editor_state("here is some textˇwith a space");
 2700    cx.update_editor(|editor, window, cx| {
 2701        editor.delete_to_previous_word_start(
 2702            &DeleteToPreviousWordStart {
 2703                ignore_newlines: true,
 2704                ignore_brackets: false,
 2705            },
 2706            window,
 2707            cx,
 2708        );
 2709    });
 2710    cx.assert_editor_state("here is some ˇwith a space");
 2711    cx.update_editor(|editor, window, cx| {
 2712        editor.delete_to_previous_word_start(
 2713            &DeleteToPreviousWordStart {
 2714                ignore_newlines: true,
 2715                ignore_brackets: false,
 2716            },
 2717            window,
 2718            cx,
 2719        );
 2720    });
 2721    // Single whitespaces are removed with the word behind them.
 2722    cx.assert_editor_state("here is ˇwith a space");
 2723    cx.update_editor(|editor, window, cx| {
 2724        editor.delete_to_previous_word_start(
 2725            &DeleteToPreviousWordStart {
 2726                ignore_newlines: true,
 2727                ignore_brackets: false,
 2728            },
 2729            window,
 2730            cx,
 2731        );
 2732    });
 2733    cx.assert_editor_state("here ˇwith a space");
 2734    cx.update_editor(|editor, window, cx| {
 2735        editor.delete_to_previous_word_start(
 2736            &DeleteToPreviousWordStart {
 2737                ignore_newlines: true,
 2738                ignore_brackets: false,
 2739            },
 2740            window,
 2741            cx,
 2742        );
 2743    });
 2744    cx.assert_editor_state("ˇwith a space");
 2745    cx.update_editor(|editor, window, cx| {
 2746        editor.delete_to_previous_word_start(
 2747            &DeleteToPreviousWordStart {
 2748                ignore_newlines: true,
 2749                ignore_brackets: false,
 2750            },
 2751            window,
 2752            cx,
 2753        );
 2754    });
 2755    cx.assert_editor_state("ˇwith a space");
 2756    cx.update_editor(|editor, window, cx| {
 2757        editor.delete_to_next_word_end(
 2758            &DeleteToNextWordEnd {
 2759                ignore_newlines: true,
 2760                ignore_brackets: false,
 2761            },
 2762            window,
 2763            cx,
 2764        );
 2765    });
 2766    // Same happens in the other direction.
 2767    cx.assert_editor_state("ˇ a space");
 2768    cx.update_editor(|editor, window, cx| {
 2769        editor.delete_to_next_word_end(
 2770            &DeleteToNextWordEnd {
 2771                ignore_newlines: true,
 2772                ignore_brackets: false,
 2773            },
 2774            window,
 2775            cx,
 2776        );
 2777    });
 2778    cx.assert_editor_state("ˇ space");
 2779    cx.update_editor(|editor, window, cx| {
 2780        editor.delete_to_next_word_end(
 2781            &DeleteToNextWordEnd {
 2782                ignore_newlines: true,
 2783                ignore_brackets: false,
 2784            },
 2785            window,
 2786            cx,
 2787        );
 2788    });
 2789    cx.assert_editor_state("ˇ");
 2790    cx.update_editor(|editor, window, cx| {
 2791        editor.delete_to_next_word_end(
 2792            &DeleteToNextWordEnd {
 2793                ignore_newlines: true,
 2794                ignore_brackets: false,
 2795            },
 2796            window,
 2797            cx,
 2798        );
 2799    });
 2800    cx.assert_editor_state("ˇ");
 2801    cx.update_editor(|editor, window, cx| {
 2802        editor.delete_to_previous_word_start(
 2803            &DeleteToPreviousWordStart {
 2804                ignore_newlines: true,
 2805                ignore_brackets: false,
 2806            },
 2807            window,
 2808            cx,
 2809        );
 2810    });
 2811    cx.assert_editor_state("ˇ");
 2812}
 2813
 2814#[gpui::test]
 2815async fn test_delete_to_bracket(cx: &mut TestAppContext) {
 2816    init_test(cx, |_| {});
 2817
 2818    let language = Arc::new(
 2819        Language::new(
 2820            LanguageConfig {
 2821                brackets: BracketPairConfig {
 2822                    pairs: vec![
 2823                        BracketPair {
 2824                            start: "\"".to_string(),
 2825                            end: "\"".to_string(),
 2826                            close: true,
 2827                            surround: true,
 2828                            newline: false,
 2829                        },
 2830                        BracketPair {
 2831                            start: "(".to_string(),
 2832                            end: ")".to_string(),
 2833                            close: true,
 2834                            surround: true,
 2835                            newline: true,
 2836                        },
 2837                    ],
 2838                    ..BracketPairConfig::default()
 2839                },
 2840                ..LanguageConfig::default()
 2841            },
 2842            Some(tree_sitter_rust::LANGUAGE.into()),
 2843        )
 2844        .with_brackets_query(
 2845            r#"
 2846                ("(" @open ")" @close)
 2847                ("\"" @open "\"" @close)
 2848            "#,
 2849        )
 2850        .unwrap(),
 2851    );
 2852
 2853    let mut cx = EditorTestContext::new(cx).await;
 2854    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2855
 2856    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2857    cx.update_editor(|editor, window, cx| {
 2858        editor.delete_to_previous_word_start(
 2859            &DeleteToPreviousWordStart {
 2860                ignore_newlines: true,
 2861                ignore_brackets: false,
 2862            },
 2863            window,
 2864            cx,
 2865        );
 2866    });
 2867    // Deletion stops before brackets if asked to not ignore them.
 2868    cx.assert_editor_state(r#"macro!("ˇCOMMENT");"#);
 2869    cx.update_editor(|editor, window, cx| {
 2870        editor.delete_to_previous_word_start(
 2871            &DeleteToPreviousWordStart {
 2872                ignore_newlines: true,
 2873                ignore_brackets: false,
 2874            },
 2875            window,
 2876            cx,
 2877        );
 2878    });
 2879    // Deletion has to remove a single bracket and then stop again.
 2880    cx.assert_editor_state(r#"macro!(ˇCOMMENT");"#);
 2881
 2882    cx.update_editor(|editor, window, cx| {
 2883        editor.delete_to_previous_word_start(
 2884            &DeleteToPreviousWordStart {
 2885                ignore_newlines: true,
 2886                ignore_brackets: false,
 2887            },
 2888            window,
 2889            cx,
 2890        );
 2891    });
 2892    cx.assert_editor_state(r#"macro!ˇCOMMENT");"#);
 2893
 2894    cx.update_editor(|editor, window, cx| {
 2895        editor.delete_to_previous_word_start(
 2896            &DeleteToPreviousWordStart {
 2897                ignore_newlines: true,
 2898                ignore_brackets: false,
 2899            },
 2900            window,
 2901            cx,
 2902        );
 2903    });
 2904    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2905
 2906    cx.update_editor(|editor, window, cx| {
 2907        editor.delete_to_previous_word_start(
 2908            &DeleteToPreviousWordStart {
 2909                ignore_newlines: true,
 2910                ignore_brackets: false,
 2911            },
 2912            window,
 2913            cx,
 2914        );
 2915    });
 2916    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2917
 2918    cx.update_editor(|editor, window, cx| {
 2919        editor.delete_to_next_word_end(
 2920            &DeleteToNextWordEnd {
 2921                ignore_newlines: true,
 2922                ignore_brackets: false,
 2923            },
 2924            window,
 2925            cx,
 2926        );
 2927    });
 2928    // Brackets on the right are not paired anymore, hence deletion does not stop at them
 2929    cx.assert_editor_state(r#"ˇ");"#);
 2930
 2931    cx.update_editor(|editor, window, cx| {
 2932        editor.delete_to_next_word_end(
 2933            &DeleteToNextWordEnd {
 2934                ignore_newlines: true,
 2935                ignore_brackets: false,
 2936            },
 2937            window,
 2938            cx,
 2939        );
 2940    });
 2941    cx.assert_editor_state(r#"ˇ"#);
 2942
 2943    cx.update_editor(|editor, window, cx| {
 2944        editor.delete_to_next_word_end(
 2945            &DeleteToNextWordEnd {
 2946                ignore_newlines: true,
 2947                ignore_brackets: false,
 2948            },
 2949            window,
 2950            cx,
 2951        );
 2952    });
 2953    cx.assert_editor_state(r#"ˇ"#);
 2954
 2955    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2956    cx.update_editor(|editor, window, cx| {
 2957        editor.delete_to_previous_word_start(
 2958            &DeleteToPreviousWordStart {
 2959                ignore_newlines: true,
 2960                ignore_brackets: true,
 2961            },
 2962            window,
 2963            cx,
 2964        );
 2965    });
 2966    cx.assert_editor_state(r#"macroˇCOMMENT");"#);
 2967}
 2968
 2969#[gpui::test]
 2970fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2971    init_test(cx, |_| {});
 2972
 2973    let editor = cx.add_window(|window, cx| {
 2974        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 2975        build_editor(buffer, window, cx)
 2976    });
 2977    let del_to_prev_word_start = DeleteToPreviousWordStart {
 2978        ignore_newlines: false,
 2979        ignore_brackets: false,
 2980    };
 2981    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 2982        ignore_newlines: true,
 2983        ignore_brackets: false,
 2984    };
 2985
 2986    _ = editor.update(cx, |editor, window, cx| {
 2987        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2988            s.select_display_ranges([
 2989                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 2990            ])
 2991        });
 2992        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2993        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 2994        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2995        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 2996        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2997        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 2998        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2999        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 3000        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 3001        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 3002        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 3003        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3004    });
 3005}
 3006
 3007#[gpui::test]
 3008fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 3009    init_test(cx, |_| {});
 3010
 3011    let editor = cx.add_window(|window, cx| {
 3012        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 3013        build_editor(buffer, window, cx)
 3014    });
 3015    let del_to_next_word_end = DeleteToNextWordEnd {
 3016        ignore_newlines: false,
 3017        ignore_brackets: false,
 3018    };
 3019    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 3020        ignore_newlines: true,
 3021        ignore_brackets: false,
 3022    };
 3023
 3024    _ = editor.update(cx, |editor, window, cx| {
 3025        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3026            s.select_display_ranges([
 3027                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 3028            ])
 3029        });
 3030        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3031        assert_eq!(
 3032            editor.buffer.read(cx).read(cx).text(),
 3033            "one\n   two\nthree\n   four"
 3034        );
 3035        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3036        assert_eq!(
 3037            editor.buffer.read(cx).read(cx).text(),
 3038            "\n   two\nthree\n   four"
 3039        );
 3040        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3041        assert_eq!(
 3042            editor.buffer.read(cx).read(cx).text(),
 3043            "two\nthree\n   four"
 3044        );
 3045        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3046        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 3047        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3048        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 3049        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3050        assert_eq!(editor.buffer.read(cx).read(cx).text(), "four");
 3051        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3052        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3053    });
 3054}
 3055
 3056#[gpui::test]
 3057fn test_newline(cx: &mut TestAppContext) {
 3058    init_test(cx, |_| {});
 3059
 3060    let editor = cx.add_window(|window, cx| {
 3061        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 3062        build_editor(buffer, window, cx)
 3063    });
 3064
 3065    _ = editor.update(cx, |editor, window, cx| {
 3066        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3067            s.select_display_ranges([
 3068                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 3069                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 3070                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 3071            ])
 3072        });
 3073
 3074        editor.newline(&Newline, window, cx);
 3075        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 3076    });
 3077}
 3078
 3079#[gpui::test]
 3080async fn test_newline_yaml(cx: &mut TestAppContext) {
 3081    init_test(cx, |_| {});
 3082
 3083    let mut cx = EditorTestContext::new(cx).await;
 3084    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3085    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3086
 3087    // Object (between 2 fields)
 3088    cx.set_state(indoc! {"
 3089    test:ˇ
 3090    hello: bye"});
 3091    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3092    cx.assert_editor_state(indoc! {"
 3093    test:
 3094        ˇ
 3095    hello: bye"});
 3096
 3097    // Object (first and single line)
 3098    cx.set_state(indoc! {"
 3099    test:ˇ"});
 3100    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3101    cx.assert_editor_state(indoc! {"
 3102    test:
 3103        ˇ"});
 3104
 3105    // Array with objects (after first element)
 3106    cx.set_state(indoc! {"
 3107    test:
 3108        - foo: barˇ"});
 3109    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3110    cx.assert_editor_state(indoc! {"
 3111    test:
 3112        - foo: bar
 3113        ˇ"});
 3114
 3115    // Array with objects and comment
 3116    cx.set_state(indoc! {"
 3117    test:
 3118        - foo: bar
 3119        - bar: # testˇ"});
 3120    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3121    cx.assert_editor_state(indoc! {"
 3122    test:
 3123        - foo: bar
 3124        - bar: # test
 3125            ˇ"});
 3126
 3127    // Array with objects (after second element)
 3128    cx.set_state(indoc! {"
 3129    test:
 3130        - foo: bar
 3131        - bar: fooˇ"});
 3132    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3133    cx.assert_editor_state(indoc! {"
 3134    test:
 3135        - foo: bar
 3136        - bar: foo
 3137        ˇ"});
 3138
 3139    // Array with strings (after first element)
 3140    cx.set_state(indoc! {"
 3141    test:
 3142        - fooˇ"});
 3143    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3144    cx.assert_editor_state(indoc! {"
 3145    test:
 3146        - foo
 3147        ˇ"});
 3148}
 3149
 3150#[gpui::test]
 3151fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 3152    init_test(cx, |_| {});
 3153
 3154    let editor = cx.add_window(|window, cx| {
 3155        let buffer = MultiBuffer::build_simple(
 3156            "
 3157                a
 3158                b(
 3159                    X
 3160                )
 3161                c(
 3162                    X
 3163                )
 3164            "
 3165            .unindent()
 3166            .as_str(),
 3167            cx,
 3168        );
 3169        let mut editor = build_editor(buffer, window, cx);
 3170        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3171            s.select_ranges([
 3172                Point::new(2, 4)..Point::new(2, 5),
 3173                Point::new(5, 4)..Point::new(5, 5),
 3174            ])
 3175        });
 3176        editor
 3177    });
 3178
 3179    _ = editor.update(cx, |editor, window, cx| {
 3180        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3181        editor.buffer.update(cx, |buffer, cx| {
 3182            buffer.edit(
 3183                [
 3184                    (Point::new(1, 2)..Point::new(3, 0), ""),
 3185                    (Point::new(4, 2)..Point::new(6, 0), ""),
 3186                ],
 3187                None,
 3188                cx,
 3189            );
 3190            assert_eq!(
 3191                buffer.read(cx).text(),
 3192                "
 3193                    a
 3194                    b()
 3195                    c()
 3196                "
 3197                .unindent()
 3198            );
 3199        });
 3200        assert_eq!(
 3201            editor.selections.ranges(&editor.display_snapshot(cx)),
 3202            &[
 3203                Point::new(1, 2)..Point::new(1, 2),
 3204                Point::new(2, 2)..Point::new(2, 2),
 3205            ],
 3206        );
 3207
 3208        editor.newline(&Newline, window, cx);
 3209        assert_eq!(
 3210            editor.text(cx),
 3211            "
 3212                a
 3213                b(
 3214                )
 3215                c(
 3216                )
 3217            "
 3218            .unindent()
 3219        );
 3220
 3221        // The selections are moved after the inserted newlines
 3222        assert_eq!(
 3223            editor.selections.ranges(&editor.display_snapshot(cx)),
 3224            &[
 3225                Point::new(2, 0)..Point::new(2, 0),
 3226                Point::new(4, 0)..Point::new(4, 0),
 3227            ],
 3228        );
 3229    });
 3230}
 3231
 3232#[gpui::test]
 3233async fn test_newline_above(cx: &mut TestAppContext) {
 3234    init_test(cx, |settings| {
 3235        settings.defaults.tab_size = NonZeroU32::new(4)
 3236    });
 3237
 3238    let language = Arc::new(
 3239        Language::new(
 3240            LanguageConfig::default(),
 3241            Some(tree_sitter_rust::LANGUAGE.into()),
 3242        )
 3243        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3244        .unwrap(),
 3245    );
 3246
 3247    let mut cx = EditorTestContext::new(cx).await;
 3248    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3249    cx.set_state(indoc! {"
 3250        const a: ˇA = (
 3251 3252                «const_functionˇ»(ˇ),
 3253                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3254 3255        ˇ);ˇ
 3256    "});
 3257
 3258    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 3259    cx.assert_editor_state(indoc! {"
 3260        ˇ
 3261        const a: A = (
 3262            ˇ
 3263            (
 3264                ˇ
 3265                ˇ
 3266                const_function(),
 3267                ˇ
 3268                ˇ
 3269                ˇ
 3270                ˇ
 3271                something_else,
 3272                ˇ
 3273            )
 3274            ˇ
 3275            ˇ
 3276        );
 3277    "});
 3278}
 3279
 3280#[gpui::test]
 3281async fn test_newline_below(cx: &mut TestAppContext) {
 3282    init_test(cx, |settings| {
 3283        settings.defaults.tab_size = NonZeroU32::new(4)
 3284    });
 3285
 3286    let language = Arc::new(
 3287        Language::new(
 3288            LanguageConfig::default(),
 3289            Some(tree_sitter_rust::LANGUAGE.into()),
 3290        )
 3291        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3292        .unwrap(),
 3293    );
 3294
 3295    let mut cx = EditorTestContext::new(cx).await;
 3296    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3297    cx.set_state(indoc! {"
 3298        const a: ˇA = (
 3299 3300                «const_functionˇ»(ˇ),
 3301                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3302 3303        ˇ);ˇ
 3304    "});
 3305
 3306    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 3307    cx.assert_editor_state(indoc! {"
 3308        const a: A = (
 3309            ˇ
 3310            (
 3311                ˇ
 3312                const_function(),
 3313                ˇ
 3314                ˇ
 3315                something_else,
 3316                ˇ
 3317                ˇ
 3318                ˇ
 3319                ˇ
 3320            )
 3321            ˇ
 3322        );
 3323        ˇ
 3324        ˇ
 3325    "});
 3326}
 3327
 3328#[gpui::test]
 3329async fn test_newline_comments(cx: &mut TestAppContext) {
 3330    init_test(cx, |settings| {
 3331        settings.defaults.tab_size = NonZeroU32::new(4)
 3332    });
 3333
 3334    let language = Arc::new(Language::new(
 3335        LanguageConfig {
 3336            line_comments: vec!["// ".into()],
 3337            ..LanguageConfig::default()
 3338        },
 3339        None,
 3340    ));
 3341    {
 3342        let mut cx = EditorTestContext::new(cx).await;
 3343        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3344        cx.set_state(indoc! {"
 3345        // Fooˇ
 3346    "});
 3347
 3348        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3349        cx.assert_editor_state(indoc! {"
 3350        // Foo
 3351        // ˇ
 3352    "});
 3353        // Ensure that we add comment prefix when existing line contains space
 3354        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3355        cx.assert_editor_state(
 3356            indoc! {"
 3357        // Foo
 3358        //s
 3359        // ˇ
 3360    "}
 3361            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3362            .as_str(),
 3363        );
 3364        // Ensure that we add comment prefix when existing line does not contain space
 3365        cx.set_state(indoc! {"
 3366        // Foo
 3367        //ˇ
 3368    "});
 3369        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3370        cx.assert_editor_state(indoc! {"
 3371        // Foo
 3372        //
 3373        // ˇ
 3374    "});
 3375        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 3376        cx.set_state(indoc! {"
 3377        ˇ// Foo
 3378    "});
 3379        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3380        cx.assert_editor_state(indoc! {"
 3381
 3382        ˇ// Foo
 3383    "});
 3384    }
 3385    // Ensure that comment continuations can be disabled.
 3386    update_test_language_settings(cx, |settings| {
 3387        settings.defaults.extend_comment_on_newline = Some(false);
 3388    });
 3389    let mut cx = EditorTestContext::new(cx).await;
 3390    cx.set_state(indoc! {"
 3391        // Fooˇ
 3392    "});
 3393    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3394    cx.assert_editor_state(indoc! {"
 3395        // Foo
 3396        ˇ
 3397    "});
 3398}
 3399
 3400#[gpui::test]
 3401async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 3402    init_test(cx, |settings| {
 3403        settings.defaults.tab_size = NonZeroU32::new(4)
 3404    });
 3405
 3406    let language = Arc::new(Language::new(
 3407        LanguageConfig {
 3408            line_comments: vec!["// ".into(), "/// ".into()],
 3409            ..LanguageConfig::default()
 3410        },
 3411        None,
 3412    ));
 3413    {
 3414        let mut cx = EditorTestContext::new(cx).await;
 3415        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3416        cx.set_state(indoc! {"
 3417        //ˇ
 3418    "});
 3419        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3420        cx.assert_editor_state(indoc! {"
 3421        //
 3422        // ˇ
 3423    "});
 3424
 3425        cx.set_state(indoc! {"
 3426        ///ˇ
 3427    "});
 3428        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3429        cx.assert_editor_state(indoc! {"
 3430        ///
 3431        /// ˇ
 3432    "});
 3433    }
 3434}
 3435
 3436#[gpui::test]
 3437async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 3438    init_test(cx, |settings| {
 3439        settings.defaults.tab_size = NonZeroU32::new(4)
 3440    });
 3441
 3442    let language = Arc::new(
 3443        Language::new(
 3444            LanguageConfig {
 3445                documentation_comment: Some(language::BlockCommentConfig {
 3446                    start: "/**".into(),
 3447                    end: "*/".into(),
 3448                    prefix: "* ".into(),
 3449                    tab_size: 1,
 3450                }),
 3451
 3452                ..LanguageConfig::default()
 3453            },
 3454            Some(tree_sitter_rust::LANGUAGE.into()),
 3455        )
 3456        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 3457        .unwrap(),
 3458    );
 3459
 3460    {
 3461        let mut cx = EditorTestContext::new(cx).await;
 3462        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3463        cx.set_state(indoc! {"
 3464        /**ˇ
 3465    "});
 3466
 3467        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3468        cx.assert_editor_state(indoc! {"
 3469        /**
 3470         * ˇ
 3471    "});
 3472        // Ensure that if cursor is before the comment start,
 3473        // we do not actually insert a comment prefix.
 3474        cx.set_state(indoc! {"
 3475        ˇ/**
 3476    "});
 3477        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3478        cx.assert_editor_state(indoc! {"
 3479
 3480        ˇ/**
 3481    "});
 3482        // Ensure that if cursor is between it doesn't add comment prefix.
 3483        cx.set_state(indoc! {"
 3484        /*ˇ*
 3485    "});
 3486        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3487        cx.assert_editor_state(indoc! {"
 3488        /*
 3489        ˇ*
 3490    "});
 3491        // Ensure that if suffix exists on same line after cursor it adds new line.
 3492        cx.set_state(indoc! {"
 3493        /**ˇ*/
 3494    "});
 3495        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3496        cx.assert_editor_state(indoc! {"
 3497        /**
 3498         * ˇ
 3499         */
 3500    "});
 3501        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3502        cx.set_state(indoc! {"
 3503        /**ˇ */
 3504    "});
 3505        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3506        cx.assert_editor_state(indoc! {"
 3507        /**
 3508         * ˇ
 3509         */
 3510    "});
 3511        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3512        cx.set_state(indoc! {"
 3513        /** ˇ*/
 3514    "});
 3515        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3516        cx.assert_editor_state(
 3517            indoc! {"
 3518        /**s
 3519         * ˇ
 3520         */
 3521    "}
 3522            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3523            .as_str(),
 3524        );
 3525        // Ensure that delimiter space is preserved when newline on already
 3526        // spaced delimiter.
 3527        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3528        cx.assert_editor_state(
 3529            indoc! {"
 3530        /**s
 3531         *s
 3532         * ˇ
 3533         */
 3534    "}
 3535            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3536            .as_str(),
 3537        );
 3538        // Ensure that delimiter space is preserved when space is not
 3539        // on existing delimiter.
 3540        cx.set_state(indoc! {"
 3541        /**
 3542 3543         */
 3544    "});
 3545        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3546        cx.assert_editor_state(indoc! {"
 3547        /**
 3548         *
 3549         * ˇ
 3550         */
 3551    "});
 3552        // Ensure that if suffix exists on same line after cursor it
 3553        // doesn't add extra new line if prefix is not on same line.
 3554        cx.set_state(indoc! {"
 3555        /**
 3556        ˇ*/
 3557    "});
 3558        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3559        cx.assert_editor_state(indoc! {"
 3560        /**
 3561
 3562        ˇ*/
 3563    "});
 3564        // Ensure that it detects suffix after existing prefix.
 3565        cx.set_state(indoc! {"
 3566        /**ˇ/
 3567    "});
 3568        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3569        cx.assert_editor_state(indoc! {"
 3570        /**
 3571        ˇ/
 3572    "});
 3573        // Ensure that if suffix exists on same line before
 3574        // cursor it does not add comment prefix.
 3575        cx.set_state(indoc! {"
 3576        /** */ˇ
 3577    "});
 3578        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3579        cx.assert_editor_state(indoc! {"
 3580        /** */
 3581        ˇ
 3582    "});
 3583        // Ensure that if suffix exists on same line before
 3584        // cursor it does not add comment prefix.
 3585        cx.set_state(indoc! {"
 3586        /**
 3587         *
 3588         */ˇ
 3589    "});
 3590        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3591        cx.assert_editor_state(indoc! {"
 3592        /**
 3593         *
 3594         */
 3595         ˇ
 3596    "});
 3597
 3598        // Ensure that inline comment followed by code
 3599        // doesn't add comment prefix on newline
 3600        cx.set_state(indoc! {"
 3601        /** */ textˇ
 3602    "});
 3603        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3604        cx.assert_editor_state(indoc! {"
 3605        /** */ text
 3606        ˇ
 3607    "});
 3608
 3609        // Ensure that text after comment end tag
 3610        // doesn't add comment prefix on newline
 3611        cx.set_state(indoc! {"
 3612        /**
 3613         *
 3614         */ˇtext
 3615    "});
 3616        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3617        cx.assert_editor_state(indoc! {"
 3618        /**
 3619         *
 3620         */
 3621         ˇtext
 3622    "});
 3623
 3624        // Ensure if not comment block it doesn't
 3625        // add comment prefix on newline
 3626        cx.set_state(indoc! {"
 3627        * textˇ
 3628    "});
 3629        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3630        cx.assert_editor_state(indoc! {"
 3631        * text
 3632        ˇ
 3633    "});
 3634    }
 3635    // Ensure that comment continuations can be disabled.
 3636    update_test_language_settings(cx, |settings| {
 3637        settings.defaults.extend_comment_on_newline = Some(false);
 3638    });
 3639    let mut cx = EditorTestContext::new(cx).await;
 3640    cx.set_state(indoc! {"
 3641        /**ˇ
 3642    "});
 3643    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3644    cx.assert_editor_state(indoc! {"
 3645        /**
 3646        ˇ
 3647    "});
 3648}
 3649
 3650#[gpui::test]
 3651async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
 3652    init_test(cx, |settings| {
 3653        settings.defaults.tab_size = NonZeroU32::new(4)
 3654    });
 3655
 3656    let lua_language = Arc::new(Language::new(
 3657        LanguageConfig {
 3658            line_comments: vec!["--".into()],
 3659            block_comment: Some(language::BlockCommentConfig {
 3660                start: "--[[".into(),
 3661                prefix: "".into(),
 3662                end: "]]".into(),
 3663                tab_size: 0,
 3664            }),
 3665            ..LanguageConfig::default()
 3666        },
 3667        None,
 3668    ));
 3669
 3670    let mut cx = EditorTestContext::new(cx).await;
 3671    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 3672
 3673    // Line with line comment should extend
 3674    cx.set_state(indoc! {"
 3675        --ˇ
 3676    "});
 3677    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3678    cx.assert_editor_state(indoc! {"
 3679        --
 3680        --ˇ
 3681    "});
 3682
 3683    // Line with block comment that matches line comment should not extend
 3684    cx.set_state(indoc! {"
 3685        --[[ˇ
 3686    "});
 3687    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3688    cx.assert_editor_state(indoc! {"
 3689        --[[
 3690        ˇ
 3691    "});
 3692}
 3693
 3694#[gpui::test]
 3695fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3696    init_test(cx, |_| {});
 3697
 3698    let editor = cx.add_window(|window, cx| {
 3699        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3700        let mut editor = build_editor(buffer, window, cx);
 3701        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3702            s.select_ranges([3..4, 11..12, 19..20])
 3703        });
 3704        editor
 3705    });
 3706
 3707    _ = editor.update(cx, |editor, window, cx| {
 3708        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3709        editor.buffer.update(cx, |buffer, cx| {
 3710            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 3711            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3712        });
 3713        assert_eq!(
 3714            editor.selections.ranges(&editor.display_snapshot(cx)),
 3715            &[2..2, 7..7, 12..12],
 3716        );
 3717
 3718        editor.insert("Z", window, cx);
 3719        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3720
 3721        // The selections are moved after the inserted characters
 3722        assert_eq!(
 3723            editor.selections.ranges(&editor.display_snapshot(cx)),
 3724            &[3..3, 9..9, 15..15],
 3725        );
 3726    });
 3727}
 3728
 3729#[gpui::test]
 3730async fn test_tab(cx: &mut TestAppContext) {
 3731    init_test(cx, |settings| {
 3732        settings.defaults.tab_size = NonZeroU32::new(3)
 3733    });
 3734
 3735    let mut cx = EditorTestContext::new(cx).await;
 3736    cx.set_state(indoc! {"
 3737        ˇabˇc
 3738        ˇ🏀ˇ🏀ˇefg
 3739 3740    "});
 3741    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3742    cx.assert_editor_state(indoc! {"
 3743           ˇab ˇc
 3744           ˇ🏀  ˇ🏀  ˇefg
 3745        d  ˇ
 3746    "});
 3747
 3748    cx.set_state(indoc! {"
 3749        a
 3750        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3751    "});
 3752    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3753    cx.assert_editor_state(indoc! {"
 3754        a
 3755           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3756    "});
 3757}
 3758
 3759#[gpui::test]
 3760async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3761    init_test(cx, |_| {});
 3762
 3763    let mut cx = EditorTestContext::new(cx).await;
 3764    let language = Arc::new(
 3765        Language::new(
 3766            LanguageConfig::default(),
 3767            Some(tree_sitter_rust::LANGUAGE.into()),
 3768        )
 3769        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3770        .unwrap(),
 3771    );
 3772    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3773
 3774    // test when all cursors are not at suggested indent
 3775    // then simply move to their suggested indent location
 3776    cx.set_state(indoc! {"
 3777        const a: B = (
 3778            c(
 3779        ˇ
 3780        ˇ    )
 3781        );
 3782    "});
 3783    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3784    cx.assert_editor_state(indoc! {"
 3785        const a: B = (
 3786            c(
 3787                ˇ
 3788            ˇ)
 3789        );
 3790    "});
 3791
 3792    // test cursor already at suggested indent not moving when
 3793    // other cursors are yet to reach their suggested indents
 3794    cx.set_state(indoc! {"
 3795        ˇ
 3796        const a: B = (
 3797            c(
 3798                d(
 3799        ˇ
 3800                )
 3801        ˇ
 3802        ˇ    )
 3803        );
 3804    "});
 3805    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3806    cx.assert_editor_state(indoc! {"
 3807        ˇ
 3808        const a: B = (
 3809            c(
 3810                d(
 3811                    ˇ
 3812                )
 3813                ˇ
 3814            ˇ)
 3815        );
 3816    "});
 3817    // test when all cursors are at suggested indent then tab is inserted
 3818    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3819    cx.assert_editor_state(indoc! {"
 3820            ˇ
 3821        const a: B = (
 3822            c(
 3823                d(
 3824                        ˇ
 3825                )
 3826                    ˇ
 3827                ˇ)
 3828        );
 3829    "});
 3830
 3831    // test when current indent is less than suggested indent,
 3832    // we adjust line to match suggested indent and move cursor to it
 3833    //
 3834    // when no other cursor is at word boundary, all of them should move
 3835    cx.set_state(indoc! {"
 3836        const a: B = (
 3837            c(
 3838                d(
 3839        ˇ
 3840        ˇ   )
 3841        ˇ   )
 3842        );
 3843    "});
 3844    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3845    cx.assert_editor_state(indoc! {"
 3846        const a: B = (
 3847            c(
 3848                d(
 3849                    ˇ
 3850                ˇ)
 3851            ˇ)
 3852        );
 3853    "});
 3854
 3855    // test when current indent is less than suggested indent,
 3856    // we adjust line to match suggested indent and move cursor to it
 3857    //
 3858    // when some other cursor is at word boundary, it should not move
 3859    cx.set_state(indoc! {"
 3860        const a: B = (
 3861            c(
 3862                d(
 3863        ˇ
 3864        ˇ   )
 3865           ˇ)
 3866        );
 3867    "});
 3868    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3869    cx.assert_editor_state(indoc! {"
 3870        const a: B = (
 3871            c(
 3872                d(
 3873                    ˇ
 3874                ˇ)
 3875            ˇ)
 3876        );
 3877    "});
 3878
 3879    // test when current indent is more than suggested indent,
 3880    // we just move cursor to current indent instead of suggested indent
 3881    //
 3882    // when no other cursor is at word boundary, all of them should move
 3883    cx.set_state(indoc! {"
 3884        const a: B = (
 3885            c(
 3886                d(
 3887        ˇ
 3888        ˇ                )
 3889        ˇ   )
 3890        );
 3891    "});
 3892    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3893    cx.assert_editor_state(indoc! {"
 3894        const a: B = (
 3895            c(
 3896                d(
 3897                    ˇ
 3898                        ˇ)
 3899            ˇ)
 3900        );
 3901    "});
 3902    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3903    cx.assert_editor_state(indoc! {"
 3904        const a: B = (
 3905            c(
 3906                d(
 3907                        ˇ
 3908                            ˇ)
 3909                ˇ)
 3910        );
 3911    "});
 3912
 3913    // test when current indent is more than suggested indent,
 3914    // we just move cursor to current indent instead of suggested indent
 3915    //
 3916    // when some other cursor is at word boundary, it doesn't move
 3917    cx.set_state(indoc! {"
 3918        const a: B = (
 3919            c(
 3920                d(
 3921        ˇ
 3922        ˇ                )
 3923            ˇ)
 3924        );
 3925    "});
 3926    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3927    cx.assert_editor_state(indoc! {"
 3928        const a: B = (
 3929            c(
 3930                d(
 3931                    ˇ
 3932                        ˇ)
 3933            ˇ)
 3934        );
 3935    "});
 3936
 3937    // handle auto-indent when there are multiple cursors on the same line
 3938    cx.set_state(indoc! {"
 3939        const a: B = (
 3940            c(
 3941        ˇ    ˇ
 3942        ˇ    )
 3943        );
 3944    "});
 3945    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3946    cx.assert_editor_state(indoc! {"
 3947        const a: B = (
 3948            c(
 3949                ˇ
 3950            ˇ)
 3951        );
 3952    "});
 3953}
 3954
 3955#[gpui::test]
 3956async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 3957    init_test(cx, |settings| {
 3958        settings.defaults.tab_size = NonZeroU32::new(3)
 3959    });
 3960
 3961    let mut cx = EditorTestContext::new(cx).await;
 3962    cx.set_state(indoc! {"
 3963         ˇ
 3964        \t ˇ
 3965        \t  ˇ
 3966        \t   ˇ
 3967         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 3968    "});
 3969
 3970    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3971    cx.assert_editor_state(indoc! {"
 3972           ˇ
 3973        \t   ˇ
 3974        \t   ˇ
 3975        \t      ˇ
 3976         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 3977    "});
 3978}
 3979
 3980#[gpui::test]
 3981async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 3982    init_test(cx, |settings| {
 3983        settings.defaults.tab_size = NonZeroU32::new(4)
 3984    });
 3985
 3986    let language = Arc::new(
 3987        Language::new(
 3988            LanguageConfig::default(),
 3989            Some(tree_sitter_rust::LANGUAGE.into()),
 3990        )
 3991        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 3992        .unwrap(),
 3993    );
 3994
 3995    let mut cx = EditorTestContext::new(cx).await;
 3996    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3997    cx.set_state(indoc! {"
 3998        fn a() {
 3999            if b {
 4000        \t ˇc
 4001            }
 4002        }
 4003    "});
 4004
 4005    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4006    cx.assert_editor_state(indoc! {"
 4007        fn a() {
 4008            if b {
 4009                ˇc
 4010            }
 4011        }
 4012    "});
 4013}
 4014
 4015#[gpui::test]
 4016async fn test_indent_outdent(cx: &mut TestAppContext) {
 4017    init_test(cx, |settings| {
 4018        settings.defaults.tab_size = NonZeroU32::new(4);
 4019    });
 4020
 4021    let mut cx = EditorTestContext::new(cx).await;
 4022
 4023    cx.set_state(indoc! {"
 4024          «oneˇ» «twoˇ»
 4025        three
 4026         four
 4027    "});
 4028    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4029    cx.assert_editor_state(indoc! {"
 4030            «oneˇ» «twoˇ»
 4031        three
 4032         four
 4033    "});
 4034
 4035    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4036    cx.assert_editor_state(indoc! {"
 4037        «oneˇ» «twoˇ»
 4038        three
 4039         four
 4040    "});
 4041
 4042    // select across line ending
 4043    cx.set_state(indoc! {"
 4044        one two
 4045        t«hree
 4046        ˇ» four
 4047    "});
 4048    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4049    cx.assert_editor_state(indoc! {"
 4050        one two
 4051            t«hree
 4052        ˇ» four
 4053    "});
 4054
 4055    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4056    cx.assert_editor_state(indoc! {"
 4057        one two
 4058        t«hree
 4059        ˇ» four
 4060    "});
 4061
 4062    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4063    cx.set_state(indoc! {"
 4064        one two
 4065        ˇthree
 4066            four
 4067    "});
 4068    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4069    cx.assert_editor_state(indoc! {"
 4070        one two
 4071            ˇthree
 4072            four
 4073    "});
 4074
 4075    cx.set_state(indoc! {"
 4076        one two
 4077        ˇ    three
 4078            four
 4079    "});
 4080    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4081    cx.assert_editor_state(indoc! {"
 4082        one two
 4083        ˇthree
 4084            four
 4085    "});
 4086}
 4087
 4088#[gpui::test]
 4089async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 4090    // This is a regression test for issue #33761
 4091    init_test(cx, |_| {});
 4092
 4093    let mut cx = EditorTestContext::new(cx).await;
 4094    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 4095    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 4096
 4097    cx.set_state(
 4098        r#"ˇ#     ingress:
 4099ˇ#         api:
 4100ˇ#             enabled: false
 4101ˇ#             pathType: Prefix
 4102ˇ#           console:
 4103ˇ#               enabled: false
 4104ˇ#               pathType: Prefix
 4105"#,
 4106    );
 4107
 4108    // Press tab to indent all lines
 4109    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4110
 4111    cx.assert_editor_state(
 4112        r#"    ˇ#     ingress:
 4113    ˇ#         api:
 4114    ˇ#             enabled: false
 4115    ˇ#             pathType: Prefix
 4116    ˇ#           console:
 4117    ˇ#               enabled: false
 4118    ˇ#               pathType: Prefix
 4119"#,
 4120    );
 4121}
 4122
 4123#[gpui::test]
 4124async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 4125    // This is a test to make sure our fix for issue #33761 didn't break anything
 4126    init_test(cx, |_| {});
 4127
 4128    let mut cx = EditorTestContext::new(cx).await;
 4129    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 4130    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 4131
 4132    cx.set_state(
 4133        r#"ˇingress:
 4134ˇ  api:
 4135ˇ    enabled: false
 4136ˇ    pathType: Prefix
 4137"#,
 4138    );
 4139
 4140    // Press tab to indent all lines
 4141    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4142
 4143    cx.assert_editor_state(
 4144        r#"ˇingress:
 4145    ˇapi:
 4146        ˇenabled: false
 4147        ˇpathType: Prefix
 4148"#,
 4149    );
 4150}
 4151
 4152#[gpui::test]
 4153async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 4154    init_test(cx, |settings| {
 4155        settings.defaults.hard_tabs = Some(true);
 4156    });
 4157
 4158    let mut cx = EditorTestContext::new(cx).await;
 4159
 4160    // select two ranges on one line
 4161    cx.set_state(indoc! {"
 4162        «oneˇ» «twoˇ»
 4163        three
 4164        four
 4165    "});
 4166    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4167    cx.assert_editor_state(indoc! {"
 4168        \t«oneˇ» «twoˇ»
 4169        three
 4170        four
 4171    "});
 4172    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4173    cx.assert_editor_state(indoc! {"
 4174        \t\t«oneˇ» «twoˇ»
 4175        three
 4176        four
 4177    "});
 4178    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4179    cx.assert_editor_state(indoc! {"
 4180        \t«oneˇ» «twoˇ»
 4181        three
 4182        four
 4183    "});
 4184    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4185    cx.assert_editor_state(indoc! {"
 4186        «oneˇ» «twoˇ»
 4187        three
 4188        four
 4189    "});
 4190
 4191    // select across a line ending
 4192    cx.set_state(indoc! {"
 4193        one two
 4194        t«hree
 4195        ˇ»four
 4196    "});
 4197    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4198    cx.assert_editor_state(indoc! {"
 4199        one two
 4200        \tt«hree
 4201        ˇ»four
 4202    "});
 4203    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4204    cx.assert_editor_state(indoc! {"
 4205        one two
 4206        \t\tt«hree
 4207        ˇ»four
 4208    "});
 4209    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4210    cx.assert_editor_state(indoc! {"
 4211        one two
 4212        \tt«hree
 4213        ˇ»four
 4214    "});
 4215    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4216    cx.assert_editor_state(indoc! {"
 4217        one two
 4218        t«hree
 4219        ˇ»four
 4220    "});
 4221
 4222    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4223    cx.set_state(indoc! {"
 4224        one two
 4225        ˇthree
 4226        four
 4227    "});
 4228    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4229    cx.assert_editor_state(indoc! {"
 4230        one two
 4231        ˇthree
 4232        four
 4233    "});
 4234    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4235    cx.assert_editor_state(indoc! {"
 4236        one two
 4237        \tˇthree
 4238        four
 4239    "});
 4240    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4241    cx.assert_editor_state(indoc! {"
 4242        one two
 4243        ˇthree
 4244        four
 4245    "});
 4246}
 4247
 4248#[gpui::test]
 4249fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 4250    init_test(cx, |settings| {
 4251        settings.languages.0.extend([
 4252            (
 4253                "TOML".into(),
 4254                LanguageSettingsContent {
 4255                    tab_size: NonZeroU32::new(2),
 4256                    ..Default::default()
 4257                },
 4258            ),
 4259            (
 4260                "Rust".into(),
 4261                LanguageSettingsContent {
 4262                    tab_size: NonZeroU32::new(4),
 4263                    ..Default::default()
 4264                },
 4265            ),
 4266        ]);
 4267    });
 4268
 4269    let toml_language = Arc::new(Language::new(
 4270        LanguageConfig {
 4271            name: "TOML".into(),
 4272            ..Default::default()
 4273        },
 4274        None,
 4275    ));
 4276    let rust_language = Arc::new(Language::new(
 4277        LanguageConfig {
 4278            name: "Rust".into(),
 4279            ..Default::default()
 4280        },
 4281        None,
 4282    ));
 4283
 4284    let toml_buffer =
 4285        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 4286    let rust_buffer =
 4287        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 4288    let multibuffer = cx.new(|cx| {
 4289        let mut multibuffer = MultiBuffer::new(ReadWrite);
 4290        multibuffer.push_excerpts(
 4291            toml_buffer.clone(),
 4292            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 4293            cx,
 4294        );
 4295        multibuffer.push_excerpts(
 4296            rust_buffer.clone(),
 4297            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 4298            cx,
 4299        );
 4300        multibuffer
 4301    });
 4302
 4303    cx.add_window(|window, cx| {
 4304        let mut editor = build_editor(multibuffer, window, cx);
 4305
 4306        assert_eq!(
 4307            editor.text(cx),
 4308            indoc! {"
 4309                a = 1
 4310                b = 2
 4311
 4312                const c: usize = 3;
 4313            "}
 4314        );
 4315
 4316        select_ranges(
 4317            &mut editor,
 4318            indoc! {"
 4319                «aˇ» = 1
 4320                b = 2
 4321
 4322                «const c:ˇ» usize = 3;
 4323            "},
 4324            window,
 4325            cx,
 4326        );
 4327
 4328        editor.tab(&Tab, window, cx);
 4329        assert_text_with_selections(
 4330            &mut editor,
 4331            indoc! {"
 4332                  «aˇ» = 1
 4333                b = 2
 4334
 4335                    «const c:ˇ» usize = 3;
 4336            "},
 4337            cx,
 4338        );
 4339        editor.backtab(&Backtab, window, cx);
 4340        assert_text_with_selections(
 4341            &mut editor,
 4342            indoc! {"
 4343                «aˇ» = 1
 4344                b = 2
 4345
 4346                «const c:ˇ» usize = 3;
 4347            "},
 4348            cx,
 4349        );
 4350
 4351        editor
 4352    });
 4353}
 4354
 4355#[gpui::test]
 4356async fn test_backspace(cx: &mut TestAppContext) {
 4357    init_test(cx, |_| {});
 4358
 4359    let mut cx = EditorTestContext::new(cx).await;
 4360
 4361    // Basic backspace
 4362    cx.set_state(indoc! {"
 4363        onˇe two three
 4364        fou«rˇ» five six
 4365        seven «ˇeight nine
 4366        »ten
 4367    "});
 4368    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4369    cx.assert_editor_state(indoc! {"
 4370        oˇe two three
 4371        fouˇ five six
 4372        seven ˇten
 4373    "});
 4374
 4375    // Test backspace inside and around indents
 4376    cx.set_state(indoc! {"
 4377        zero
 4378            ˇone
 4379                ˇtwo
 4380            ˇ ˇ ˇ  three
 4381        ˇ  ˇ  four
 4382    "});
 4383    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4384    cx.assert_editor_state(indoc! {"
 4385        zero
 4386        ˇone
 4387            ˇtwo
 4388        ˇ  threeˇ  four
 4389    "});
 4390}
 4391
 4392#[gpui::test]
 4393async fn test_delete(cx: &mut TestAppContext) {
 4394    init_test(cx, |_| {});
 4395
 4396    let mut cx = EditorTestContext::new(cx).await;
 4397    cx.set_state(indoc! {"
 4398        onˇe two three
 4399        fou«rˇ» five six
 4400        seven «ˇeight nine
 4401        »ten
 4402    "});
 4403    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 4404    cx.assert_editor_state(indoc! {"
 4405        onˇ two three
 4406        fouˇ five six
 4407        seven ˇten
 4408    "});
 4409}
 4410
 4411#[gpui::test]
 4412fn test_delete_line(cx: &mut TestAppContext) {
 4413    init_test(cx, |_| {});
 4414
 4415    let editor = cx.add_window(|window, cx| {
 4416        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4417        build_editor(buffer, window, cx)
 4418    });
 4419    _ = editor.update(cx, |editor, window, cx| {
 4420        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4421            s.select_display_ranges([
 4422                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4423                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4424                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4425            ])
 4426        });
 4427        editor.delete_line(&DeleteLine, window, cx);
 4428        assert_eq!(editor.display_text(cx), "ghi");
 4429        assert_eq!(
 4430            display_ranges(editor, cx),
 4431            vec![
 4432                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 4433                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4434            ]
 4435        );
 4436    });
 4437
 4438    let editor = cx.add_window(|window, cx| {
 4439        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4440        build_editor(buffer, window, cx)
 4441    });
 4442    _ = editor.update(cx, |editor, window, cx| {
 4443        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4444            s.select_display_ranges([
 4445                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 4446            ])
 4447        });
 4448        editor.delete_line(&DeleteLine, window, cx);
 4449        assert_eq!(editor.display_text(cx), "ghi\n");
 4450        assert_eq!(
 4451            display_ranges(editor, cx),
 4452            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 4453        );
 4454    });
 4455
 4456    let editor = cx.add_window(|window, cx| {
 4457        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n\njkl\nmno", cx);
 4458        build_editor(buffer, window, cx)
 4459    });
 4460    _ = editor.update(cx, |editor, window, cx| {
 4461        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4462            s.select_display_ranges([
 4463                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(2), 1)
 4464            ])
 4465        });
 4466        editor.delete_line(&DeleteLine, window, cx);
 4467        assert_eq!(editor.display_text(cx), "\njkl\nmno");
 4468        assert_eq!(
 4469            display_ranges(editor, cx),
 4470            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 4471        );
 4472    });
 4473}
 4474
 4475#[gpui::test]
 4476fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 4477    init_test(cx, |_| {});
 4478
 4479    cx.add_window(|window, cx| {
 4480        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4481        let mut editor = build_editor(buffer.clone(), window, cx);
 4482        let buffer = buffer.read(cx).as_singleton().unwrap();
 4483
 4484        assert_eq!(
 4485            editor
 4486                .selections
 4487                .ranges::<Point>(&editor.display_snapshot(cx)),
 4488            &[Point::new(0, 0)..Point::new(0, 0)]
 4489        );
 4490
 4491        // When on single line, replace newline at end by space
 4492        editor.join_lines(&JoinLines, window, cx);
 4493        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4494        assert_eq!(
 4495            editor
 4496                .selections
 4497                .ranges::<Point>(&editor.display_snapshot(cx)),
 4498            &[Point::new(0, 3)..Point::new(0, 3)]
 4499        );
 4500
 4501        // When multiple lines are selected, remove newlines that are spanned by the selection
 4502        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4503            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 4504        });
 4505        editor.join_lines(&JoinLines, window, cx);
 4506        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 4507        assert_eq!(
 4508            editor
 4509                .selections
 4510                .ranges::<Point>(&editor.display_snapshot(cx)),
 4511            &[Point::new(0, 11)..Point::new(0, 11)]
 4512        );
 4513
 4514        // Undo should be transactional
 4515        editor.undo(&Undo, window, cx);
 4516        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4517        assert_eq!(
 4518            editor
 4519                .selections
 4520                .ranges::<Point>(&editor.display_snapshot(cx)),
 4521            &[Point::new(0, 5)..Point::new(2, 2)]
 4522        );
 4523
 4524        // When joining an empty line don't insert a space
 4525        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4526            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 4527        });
 4528        editor.join_lines(&JoinLines, window, cx);
 4529        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 4530        assert_eq!(
 4531            editor
 4532                .selections
 4533                .ranges::<Point>(&editor.display_snapshot(cx)),
 4534            [Point::new(2, 3)..Point::new(2, 3)]
 4535        );
 4536
 4537        // We can remove trailing newlines
 4538        editor.join_lines(&JoinLines, window, cx);
 4539        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4540        assert_eq!(
 4541            editor
 4542                .selections
 4543                .ranges::<Point>(&editor.display_snapshot(cx)),
 4544            [Point::new(2, 3)..Point::new(2, 3)]
 4545        );
 4546
 4547        // We don't blow up on the last line
 4548        editor.join_lines(&JoinLines, window, cx);
 4549        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4550        assert_eq!(
 4551            editor
 4552                .selections
 4553                .ranges::<Point>(&editor.display_snapshot(cx)),
 4554            [Point::new(2, 3)..Point::new(2, 3)]
 4555        );
 4556
 4557        // reset to test indentation
 4558        editor.buffer.update(cx, |buffer, cx| {
 4559            buffer.edit(
 4560                [
 4561                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 4562                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 4563                ],
 4564                None,
 4565                cx,
 4566            )
 4567        });
 4568
 4569        // We remove any leading spaces
 4570        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 4571        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4572            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 4573        });
 4574        editor.join_lines(&JoinLines, window, cx);
 4575        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 4576
 4577        // We don't insert a space for a line containing only spaces
 4578        editor.join_lines(&JoinLines, window, cx);
 4579        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 4580
 4581        // We ignore any leading tabs
 4582        editor.join_lines(&JoinLines, window, cx);
 4583        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 4584
 4585        editor
 4586    });
 4587}
 4588
 4589#[gpui::test]
 4590fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 4591    init_test(cx, |_| {});
 4592
 4593    cx.add_window(|window, cx| {
 4594        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4595        let mut editor = build_editor(buffer.clone(), window, cx);
 4596        let buffer = buffer.read(cx).as_singleton().unwrap();
 4597
 4598        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4599            s.select_ranges([
 4600                Point::new(0, 2)..Point::new(1, 1),
 4601                Point::new(1, 2)..Point::new(1, 2),
 4602                Point::new(3, 1)..Point::new(3, 2),
 4603            ])
 4604        });
 4605
 4606        editor.join_lines(&JoinLines, window, cx);
 4607        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 4608
 4609        assert_eq!(
 4610            editor
 4611                .selections
 4612                .ranges::<Point>(&editor.display_snapshot(cx)),
 4613            [
 4614                Point::new(0, 7)..Point::new(0, 7),
 4615                Point::new(1, 3)..Point::new(1, 3)
 4616            ]
 4617        );
 4618        editor
 4619    });
 4620}
 4621
 4622#[gpui::test]
 4623async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4624    init_test(cx, |_| {});
 4625
 4626    let mut cx = EditorTestContext::new(cx).await;
 4627
 4628    let diff_base = r#"
 4629        Line 0
 4630        Line 1
 4631        Line 2
 4632        Line 3
 4633        "#
 4634    .unindent();
 4635
 4636    cx.set_state(
 4637        &r#"
 4638        ˇLine 0
 4639        Line 1
 4640        Line 2
 4641        Line 3
 4642        "#
 4643        .unindent(),
 4644    );
 4645
 4646    cx.set_head_text(&diff_base);
 4647    executor.run_until_parked();
 4648
 4649    // Join lines
 4650    cx.update_editor(|editor, window, cx| {
 4651        editor.join_lines(&JoinLines, window, cx);
 4652    });
 4653    executor.run_until_parked();
 4654
 4655    cx.assert_editor_state(
 4656        &r#"
 4657        Line 0ˇ Line 1
 4658        Line 2
 4659        Line 3
 4660        "#
 4661        .unindent(),
 4662    );
 4663    // Join again
 4664    cx.update_editor(|editor, window, cx| {
 4665        editor.join_lines(&JoinLines, window, cx);
 4666    });
 4667    executor.run_until_parked();
 4668
 4669    cx.assert_editor_state(
 4670        &r#"
 4671        Line 0 Line 1ˇ Line 2
 4672        Line 3
 4673        "#
 4674        .unindent(),
 4675    );
 4676}
 4677
 4678#[gpui::test]
 4679async fn test_custom_newlines_cause_no_false_positive_diffs(
 4680    executor: BackgroundExecutor,
 4681    cx: &mut TestAppContext,
 4682) {
 4683    init_test(cx, |_| {});
 4684    let mut cx = EditorTestContext::new(cx).await;
 4685    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4686    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4687    executor.run_until_parked();
 4688
 4689    cx.update_editor(|editor, window, cx| {
 4690        let snapshot = editor.snapshot(window, cx);
 4691        assert_eq!(
 4692            snapshot
 4693                .buffer_snapshot()
 4694                .diff_hunks_in_range(0..snapshot.buffer_snapshot().len())
 4695                .collect::<Vec<_>>(),
 4696            Vec::new(),
 4697            "Should not have any diffs for files with custom newlines"
 4698        );
 4699    });
 4700}
 4701
 4702#[gpui::test]
 4703async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4704    init_test(cx, |_| {});
 4705
 4706    let mut cx = EditorTestContext::new(cx).await;
 4707
 4708    // Test sort_lines_case_insensitive()
 4709    cx.set_state(indoc! {"
 4710        «z
 4711        y
 4712        x
 4713        Z
 4714        Y
 4715        Xˇ»
 4716    "});
 4717    cx.update_editor(|e, window, cx| {
 4718        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4719    });
 4720    cx.assert_editor_state(indoc! {"
 4721        «x
 4722        X
 4723        y
 4724        Y
 4725        z
 4726        Zˇ»
 4727    "});
 4728
 4729    // Test sort_lines_by_length()
 4730    //
 4731    // Demonstrates:
 4732    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4733    // - sort is stable
 4734    cx.set_state(indoc! {"
 4735        «123
 4736        æ
 4737        12
 4738 4739        1
 4740        æˇ»
 4741    "});
 4742    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4743    cx.assert_editor_state(indoc! {"
 4744        «æ
 4745 4746        1
 4747        æ
 4748        12
 4749        123ˇ»
 4750    "});
 4751
 4752    // Test reverse_lines()
 4753    cx.set_state(indoc! {"
 4754        «5
 4755        4
 4756        3
 4757        2
 4758        1ˇ»
 4759    "});
 4760    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4761    cx.assert_editor_state(indoc! {"
 4762        «1
 4763        2
 4764        3
 4765        4
 4766        5ˇ»
 4767    "});
 4768
 4769    // Skip testing shuffle_line()
 4770
 4771    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4772    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4773
 4774    // Don't manipulate when cursor is on single line, but expand the selection
 4775    cx.set_state(indoc! {"
 4776        ddˇdd
 4777        ccc
 4778        bb
 4779        a
 4780    "});
 4781    cx.update_editor(|e, window, cx| {
 4782        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4783    });
 4784    cx.assert_editor_state(indoc! {"
 4785        «ddddˇ»
 4786        ccc
 4787        bb
 4788        a
 4789    "});
 4790
 4791    // Basic manipulate case
 4792    // Start selection moves to column 0
 4793    // End of selection shrinks to fit shorter line
 4794    cx.set_state(indoc! {"
 4795        dd«d
 4796        ccc
 4797        bb
 4798        aaaaaˇ»
 4799    "});
 4800    cx.update_editor(|e, window, cx| {
 4801        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4802    });
 4803    cx.assert_editor_state(indoc! {"
 4804        «aaaaa
 4805        bb
 4806        ccc
 4807        dddˇ»
 4808    "});
 4809
 4810    // Manipulate case with newlines
 4811    cx.set_state(indoc! {"
 4812        dd«d
 4813        ccc
 4814
 4815        bb
 4816        aaaaa
 4817
 4818        ˇ»
 4819    "});
 4820    cx.update_editor(|e, window, cx| {
 4821        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4822    });
 4823    cx.assert_editor_state(indoc! {"
 4824        «
 4825
 4826        aaaaa
 4827        bb
 4828        ccc
 4829        dddˇ»
 4830
 4831    "});
 4832
 4833    // Adding new line
 4834    cx.set_state(indoc! {"
 4835        aa«a
 4836        bbˇ»b
 4837    "});
 4838    cx.update_editor(|e, window, cx| {
 4839        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4840    });
 4841    cx.assert_editor_state(indoc! {"
 4842        «aaa
 4843        bbb
 4844        added_lineˇ»
 4845    "});
 4846
 4847    // Removing line
 4848    cx.set_state(indoc! {"
 4849        aa«a
 4850        bbbˇ»
 4851    "});
 4852    cx.update_editor(|e, window, cx| {
 4853        e.manipulate_immutable_lines(window, cx, |lines| {
 4854            lines.pop();
 4855        })
 4856    });
 4857    cx.assert_editor_state(indoc! {"
 4858        «aaaˇ»
 4859    "});
 4860
 4861    // Removing all lines
 4862    cx.set_state(indoc! {"
 4863        aa«a
 4864        bbbˇ»
 4865    "});
 4866    cx.update_editor(|e, window, cx| {
 4867        e.manipulate_immutable_lines(window, cx, |lines| {
 4868            lines.drain(..);
 4869        })
 4870    });
 4871    cx.assert_editor_state(indoc! {"
 4872        ˇ
 4873    "});
 4874}
 4875
 4876#[gpui::test]
 4877async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4878    init_test(cx, |_| {});
 4879
 4880    let mut cx = EditorTestContext::new(cx).await;
 4881
 4882    // Consider continuous selection as single selection
 4883    cx.set_state(indoc! {"
 4884        Aaa«aa
 4885        cˇ»c«c
 4886        bb
 4887        aaaˇ»aa
 4888    "});
 4889    cx.update_editor(|e, window, cx| {
 4890        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4891    });
 4892    cx.assert_editor_state(indoc! {"
 4893        «Aaaaa
 4894        ccc
 4895        bb
 4896        aaaaaˇ»
 4897    "});
 4898
 4899    cx.set_state(indoc! {"
 4900        Aaa«aa
 4901        cˇ»c«c
 4902        bb
 4903        aaaˇ»aa
 4904    "});
 4905    cx.update_editor(|e, window, cx| {
 4906        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4907    });
 4908    cx.assert_editor_state(indoc! {"
 4909        «Aaaaa
 4910        ccc
 4911        bbˇ»
 4912    "});
 4913
 4914    // Consider non continuous selection as distinct dedup operations
 4915    cx.set_state(indoc! {"
 4916        «aaaaa
 4917        bb
 4918        aaaaa
 4919        aaaaaˇ»
 4920
 4921        aaa«aaˇ»
 4922    "});
 4923    cx.update_editor(|e, window, cx| {
 4924        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4925    });
 4926    cx.assert_editor_state(indoc! {"
 4927        «aaaaa
 4928        bbˇ»
 4929
 4930        «aaaaaˇ»
 4931    "});
 4932}
 4933
 4934#[gpui::test]
 4935async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4936    init_test(cx, |_| {});
 4937
 4938    let mut cx = EditorTestContext::new(cx).await;
 4939
 4940    cx.set_state(indoc! {"
 4941        «Aaa
 4942        aAa
 4943        Aaaˇ»
 4944    "});
 4945    cx.update_editor(|e, window, cx| {
 4946        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4947    });
 4948    cx.assert_editor_state(indoc! {"
 4949        «Aaa
 4950        aAaˇ»
 4951    "});
 4952
 4953    cx.set_state(indoc! {"
 4954        «Aaa
 4955        aAa
 4956        aaAˇ»
 4957    "});
 4958    cx.update_editor(|e, window, cx| {
 4959        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4960    });
 4961    cx.assert_editor_state(indoc! {"
 4962        «Aaaˇ»
 4963    "});
 4964}
 4965
 4966#[gpui::test]
 4967async fn test_wrap_in_tag_single_selection(cx: &mut TestAppContext) {
 4968    init_test(cx, |_| {});
 4969
 4970    let mut cx = EditorTestContext::new(cx).await;
 4971
 4972    let js_language = Arc::new(Language::new(
 4973        LanguageConfig {
 4974            name: "JavaScript".into(),
 4975            wrap_characters: Some(language::WrapCharactersConfig {
 4976                start_prefix: "<".into(),
 4977                start_suffix: ">".into(),
 4978                end_prefix: "</".into(),
 4979                end_suffix: ">".into(),
 4980            }),
 4981            ..LanguageConfig::default()
 4982        },
 4983        None,
 4984    ));
 4985
 4986    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 4987
 4988    cx.set_state(indoc! {"
 4989        «testˇ»
 4990    "});
 4991    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4992    cx.assert_editor_state(indoc! {"
 4993        <«ˇ»>test</«ˇ»>
 4994    "});
 4995
 4996    cx.set_state(indoc! {"
 4997        «test
 4998         testˇ»
 4999    "});
 5000    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5001    cx.assert_editor_state(indoc! {"
 5002        <«ˇ»>test
 5003         test</«ˇ»>
 5004    "});
 5005
 5006    cx.set_state(indoc! {"
 5007        teˇst
 5008    "});
 5009    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5010    cx.assert_editor_state(indoc! {"
 5011        te<«ˇ»></«ˇ»>st
 5012    "});
 5013}
 5014
 5015#[gpui::test]
 5016async fn test_wrap_in_tag_multi_selection(cx: &mut TestAppContext) {
 5017    init_test(cx, |_| {});
 5018
 5019    let mut cx = EditorTestContext::new(cx).await;
 5020
 5021    let js_language = Arc::new(Language::new(
 5022        LanguageConfig {
 5023            name: "JavaScript".into(),
 5024            wrap_characters: Some(language::WrapCharactersConfig {
 5025                start_prefix: "<".into(),
 5026                start_suffix: ">".into(),
 5027                end_prefix: "</".into(),
 5028                end_suffix: ">".into(),
 5029            }),
 5030            ..LanguageConfig::default()
 5031        },
 5032        None,
 5033    ));
 5034
 5035    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 5036
 5037    cx.set_state(indoc! {"
 5038        «testˇ»
 5039        «testˇ» «testˇ»
 5040        «testˇ»
 5041    "});
 5042    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5043    cx.assert_editor_state(indoc! {"
 5044        <«ˇ»>test</«ˇ»>
 5045        <«ˇ»>test</«ˇ»> <«ˇ»>test</«ˇ»>
 5046        <«ˇ»>test</«ˇ»>
 5047    "});
 5048
 5049    cx.set_state(indoc! {"
 5050        «test
 5051         testˇ»
 5052        «test
 5053         testˇ»
 5054    "});
 5055    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5056    cx.assert_editor_state(indoc! {"
 5057        <«ˇ»>test
 5058         test</«ˇ»>
 5059        <«ˇ»>test
 5060         test</«ˇ»>
 5061    "});
 5062}
 5063
 5064#[gpui::test]
 5065async fn test_wrap_in_tag_does_nothing_in_unsupported_languages(cx: &mut TestAppContext) {
 5066    init_test(cx, |_| {});
 5067
 5068    let mut cx = EditorTestContext::new(cx).await;
 5069
 5070    let plaintext_language = Arc::new(Language::new(
 5071        LanguageConfig {
 5072            name: "Plain Text".into(),
 5073            ..LanguageConfig::default()
 5074        },
 5075        None,
 5076    ));
 5077
 5078    cx.update_buffer(|buffer, cx| buffer.set_language(Some(plaintext_language), cx));
 5079
 5080    cx.set_state(indoc! {"
 5081        «testˇ»
 5082    "});
 5083    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5084    cx.assert_editor_state(indoc! {"
 5085      «testˇ»
 5086    "});
 5087}
 5088
 5089#[gpui::test]
 5090async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 5091    init_test(cx, |_| {});
 5092
 5093    let mut cx = EditorTestContext::new(cx).await;
 5094
 5095    // Manipulate with multiple selections on a single line
 5096    cx.set_state(indoc! {"
 5097        dd«dd
 5098        cˇ»c«c
 5099        bb
 5100        aaaˇ»aa
 5101    "});
 5102    cx.update_editor(|e, window, cx| {
 5103        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5104    });
 5105    cx.assert_editor_state(indoc! {"
 5106        «aaaaa
 5107        bb
 5108        ccc
 5109        ddddˇ»
 5110    "});
 5111
 5112    // Manipulate with multiple disjoin selections
 5113    cx.set_state(indoc! {"
 5114 5115        4
 5116        3
 5117        2
 5118        1ˇ»
 5119
 5120        dd«dd
 5121        ccc
 5122        bb
 5123        aaaˇ»aa
 5124    "});
 5125    cx.update_editor(|e, window, cx| {
 5126        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5127    });
 5128    cx.assert_editor_state(indoc! {"
 5129        «1
 5130        2
 5131        3
 5132        4
 5133        5ˇ»
 5134
 5135        «aaaaa
 5136        bb
 5137        ccc
 5138        ddddˇ»
 5139    "});
 5140
 5141    // Adding lines on each selection
 5142    cx.set_state(indoc! {"
 5143 5144        1ˇ»
 5145
 5146        bb«bb
 5147        aaaˇ»aa
 5148    "});
 5149    cx.update_editor(|e, window, cx| {
 5150        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 5151    });
 5152    cx.assert_editor_state(indoc! {"
 5153        «2
 5154        1
 5155        added lineˇ»
 5156
 5157        «bbbb
 5158        aaaaa
 5159        added lineˇ»
 5160    "});
 5161
 5162    // Removing lines on each selection
 5163    cx.set_state(indoc! {"
 5164 5165        1ˇ»
 5166
 5167        bb«bb
 5168        aaaˇ»aa
 5169    "});
 5170    cx.update_editor(|e, window, cx| {
 5171        e.manipulate_immutable_lines(window, cx, |lines| {
 5172            lines.pop();
 5173        })
 5174    });
 5175    cx.assert_editor_state(indoc! {"
 5176        «2ˇ»
 5177
 5178        «bbbbˇ»
 5179    "});
 5180}
 5181
 5182#[gpui::test]
 5183async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 5184    init_test(cx, |settings| {
 5185        settings.defaults.tab_size = NonZeroU32::new(3)
 5186    });
 5187
 5188    let mut cx = EditorTestContext::new(cx).await;
 5189
 5190    // MULTI SELECTION
 5191    // Ln.1 "«" tests empty lines
 5192    // Ln.9 tests just leading whitespace
 5193    cx.set_state(indoc! {"
 5194        «
 5195        abc                 // No indentationˇ»
 5196        «\tabc              // 1 tabˇ»
 5197        \t\tabc «      ˇ»   // 2 tabs
 5198        \t ab«c             // Tab followed by space
 5199         \tabc              // Space followed by tab (3 spaces should be the result)
 5200        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5201           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 5202        \t
 5203        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5204    "});
 5205    cx.update_editor(|e, window, cx| {
 5206        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5207    });
 5208    cx.assert_editor_state(
 5209        indoc! {"
 5210            «
 5211            abc                 // No indentation
 5212               abc              // 1 tab
 5213                  abc          // 2 tabs
 5214                abc             // Tab followed by space
 5215               abc              // Space followed by tab (3 spaces should be the result)
 5216                           abc   // Mixed indentation (tab conversion depends on the column)
 5217               abc         // Already space indented
 5218               ·
 5219               abc\tdef          // Only the leading tab is manipulatedˇ»
 5220        "}
 5221        .replace("·", "")
 5222        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5223    );
 5224
 5225    // Test on just a few lines, the others should remain unchanged
 5226    // Only lines (3, 5, 10, 11) should change
 5227    cx.set_state(
 5228        indoc! {"
 5229            ·
 5230            abc                 // No indentation
 5231            \tabcˇ               // 1 tab
 5232            \t\tabc             // 2 tabs
 5233            \t abcˇ              // Tab followed by space
 5234             \tabc              // Space followed by tab (3 spaces should be the result)
 5235            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5236               abc              // Already space indented
 5237            «\t
 5238            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5239        "}
 5240        .replace("·", "")
 5241        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5242    );
 5243    cx.update_editor(|e, window, cx| {
 5244        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5245    });
 5246    cx.assert_editor_state(
 5247        indoc! {"
 5248            ·
 5249            abc                 // No indentation
 5250            «   abc               // 1 tabˇ»
 5251            \t\tabc             // 2 tabs
 5252            «    abc              // Tab followed by spaceˇ»
 5253             \tabc              // Space followed by tab (3 spaces should be the result)
 5254            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5255               abc              // Already space indented
 5256            «   ·
 5257               abc\tdef          // Only the leading tab is manipulatedˇ»
 5258        "}
 5259        .replace("·", "")
 5260        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5261    );
 5262
 5263    // SINGLE SELECTION
 5264    // Ln.1 "«" tests empty lines
 5265    // Ln.9 tests just leading whitespace
 5266    cx.set_state(indoc! {"
 5267        «
 5268        abc                 // No indentation
 5269        \tabc               // 1 tab
 5270        \t\tabc             // 2 tabs
 5271        \t abc              // Tab followed by space
 5272         \tabc              // Space followed by tab (3 spaces should be the result)
 5273        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5274           abc              // Already space indented
 5275        \t
 5276        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5277    "});
 5278    cx.update_editor(|e, window, cx| {
 5279        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5280    });
 5281    cx.assert_editor_state(
 5282        indoc! {"
 5283            «
 5284            abc                 // No indentation
 5285               abc               // 1 tab
 5286                  abc             // 2 tabs
 5287                abc              // Tab followed by space
 5288               abc              // Space followed by tab (3 spaces should be the result)
 5289                           abc   // Mixed indentation (tab conversion depends on the column)
 5290               abc              // Already space indented
 5291               ·
 5292               abc\tdef          // Only the leading tab is manipulatedˇ»
 5293        "}
 5294        .replace("·", "")
 5295        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5296    );
 5297}
 5298
 5299#[gpui::test]
 5300async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 5301    init_test(cx, |settings| {
 5302        settings.defaults.tab_size = NonZeroU32::new(3)
 5303    });
 5304
 5305    let mut cx = EditorTestContext::new(cx).await;
 5306
 5307    // MULTI SELECTION
 5308    // Ln.1 "«" tests empty lines
 5309    // Ln.11 tests just leading whitespace
 5310    cx.set_state(indoc! {"
 5311        «
 5312        abˇ»ˇc                 // No indentation
 5313         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 5314          abc  «             // 2 spaces (< 3 so dont convert)
 5315           abc              // 3 spaces (convert)
 5316             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 5317        «\tˇ»\t«\tˇ»abc           // Already tab indented
 5318        «\t abc              // Tab followed by space
 5319         \tabc              // Space followed by tab (should be consumed due to tab)
 5320        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5321           \tˇ»  «\t
 5322           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 5323    "});
 5324    cx.update_editor(|e, window, cx| {
 5325        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5326    });
 5327    cx.assert_editor_state(indoc! {"
 5328        «
 5329        abc                 // No indentation
 5330         abc                // 1 space (< 3 so dont convert)
 5331          abc               // 2 spaces (< 3 so dont convert)
 5332        \tabc              // 3 spaces (convert)
 5333        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5334        \t\t\tabc           // Already tab indented
 5335        \t abc              // Tab followed by space
 5336        \tabc              // Space followed by tab (should be consumed due to tab)
 5337        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5338        \t\t\t
 5339        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5340    "});
 5341
 5342    // Test on just a few lines, the other should remain unchanged
 5343    // Only lines (4, 8, 11, 12) should change
 5344    cx.set_state(
 5345        indoc! {"
 5346            ·
 5347            abc                 // No indentation
 5348             abc                // 1 space (< 3 so dont convert)
 5349              abc               // 2 spaces (< 3 so dont convert)
 5350            «   abc              // 3 spaces (convert)ˇ»
 5351                 abc            // 5 spaces (1 tab + 2 spaces)
 5352            \t\t\tabc           // Already tab indented
 5353            \t abc              // Tab followed by space
 5354             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 5355               \t\t  \tabc      // Mixed indentation
 5356            \t \t  \t   \tabc   // Mixed indentation
 5357               \t  \tˇ
 5358            «   abc   \t         // Only the leading spaces should be convertedˇ»
 5359        "}
 5360        .replace("·", "")
 5361        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5362    );
 5363    cx.update_editor(|e, window, cx| {
 5364        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5365    });
 5366    cx.assert_editor_state(
 5367        indoc! {"
 5368            ·
 5369            abc                 // No indentation
 5370             abc                // 1 space (< 3 so dont convert)
 5371              abc               // 2 spaces (< 3 so dont convert)
 5372            «\tabc              // 3 spaces (convert)ˇ»
 5373                 abc            // 5 spaces (1 tab + 2 spaces)
 5374            \t\t\tabc           // Already tab indented
 5375            \t abc              // Tab followed by space
 5376            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 5377               \t\t  \tabc      // Mixed indentation
 5378            \t \t  \t   \tabc   // Mixed indentation
 5379            «\t\t\t
 5380            \tabc   \t         // Only the leading spaces should be convertedˇ»
 5381        "}
 5382        .replace("·", "")
 5383        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5384    );
 5385
 5386    // SINGLE SELECTION
 5387    // Ln.1 "«" tests empty lines
 5388    // Ln.11 tests just leading whitespace
 5389    cx.set_state(indoc! {"
 5390        «
 5391        abc                 // No indentation
 5392         abc                // 1 space (< 3 so dont convert)
 5393          abc               // 2 spaces (< 3 so dont convert)
 5394           abc              // 3 spaces (convert)
 5395             abc            // 5 spaces (1 tab + 2 spaces)
 5396        \t\t\tabc           // Already tab indented
 5397        \t abc              // Tab followed by space
 5398         \tabc              // Space followed by tab (should be consumed due to tab)
 5399        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5400           \t  \t
 5401           abc   \t         // Only the leading spaces should be convertedˇ»
 5402    "});
 5403    cx.update_editor(|e, window, cx| {
 5404        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5405    });
 5406    cx.assert_editor_state(indoc! {"
 5407        «
 5408        abc                 // No indentation
 5409         abc                // 1 space (< 3 so dont convert)
 5410          abc               // 2 spaces (< 3 so dont convert)
 5411        \tabc              // 3 spaces (convert)
 5412        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5413        \t\t\tabc           // Already tab indented
 5414        \t abc              // Tab followed by space
 5415        \tabc              // Space followed by tab (should be consumed due to tab)
 5416        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5417        \t\t\t
 5418        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5419    "});
 5420}
 5421
 5422#[gpui::test]
 5423async fn test_toggle_case(cx: &mut TestAppContext) {
 5424    init_test(cx, |_| {});
 5425
 5426    let mut cx = EditorTestContext::new(cx).await;
 5427
 5428    // If all lower case -> upper case
 5429    cx.set_state(indoc! {"
 5430        «hello worldˇ»
 5431    "});
 5432    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5433    cx.assert_editor_state(indoc! {"
 5434        «HELLO WORLDˇ»
 5435    "});
 5436
 5437    // If all upper case -> lower case
 5438    cx.set_state(indoc! {"
 5439        «HELLO WORLDˇ»
 5440    "});
 5441    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5442    cx.assert_editor_state(indoc! {"
 5443        «hello worldˇ»
 5444    "});
 5445
 5446    // If any upper case characters are identified -> lower case
 5447    // This matches JetBrains IDEs
 5448    cx.set_state(indoc! {"
 5449        «hEllo worldˇ»
 5450    "});
 5451    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5452    cx.assert_editor_state(indoc! {"
 5453        «hello worldˇ»
 5454    "});
 5455}
 5456
 5457#[gpui::test]
 5458async fn test_convert_to_sentence_case(cx: &mut TestAppContext) {
 5459    init_test(cx, |_| {});
 5460
 5461    let mut cx = EditorTestContext::new(cx).await;
 5462
 5463    cx.set_state(indoc! {"
 5464        «implement-windows-supportˇ»
 5465    "});
 5466    cx.update_editor(|e, window, cx| {
 5467        e.convert_to_sentence_case(&ConvertToSentenceCase, window, cx)
 5468    });
 5469    cx.assert_editor_state(indoc! {"
 5470        «Implement windows supportˇ»
 5471    "});
 5472}
 5473
 5474#[gpui::test]
 5475async fn test_manipulate_text(cx: &mut TestAppContext) {
 5476    init_test(cx, |_| {});
 5477
 5478    let mut cx = EditorTestContext::new(cx).await;
 5479
 5480    // Test convert_to_upper_case()
 5481    cx.set_state(indoc! {"
 5482        «hello worldˇ»
 5483    "});
 5484    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5485    cx.assert_editor_state(indoc! {"
 5486        «HELLO WORLDˇ»
 5487    "});
 5488
 5489    // Test convert_to_lower_case()
 5490    cx.set_state(indoc! {"
 5491        «HELLO WORLDˇ»
 5492    "});
 5493    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 5494    cx.assert_editor_state(indoc! {"
 5495        «hello worldˇ»
 5496    "});
 5497
 5498    // Test multiple line, single selection case
 5499    cx.set_state(indoc! {"
 5500        «The quick brown
 5501        fox jumps over
 5502        the lazy dogˇ»
 5503    "});
 5504    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 5505    cx.assert_editor_state(indoc! {"
 5506        «The Quick Brown
 5507        Fox Jumps Over
 5508        The Lazy Dogˇ»
 5509    "});
 5510
 5511    // Test multiple line, single selection case
 5512    cx.set_state(indoc! {"
 5513        «The quick brown
 5514        fox jumps over
 5515        the lazy dogˇ»
 5516    "});
 5517    cx.update_editor(|e, window, cx| {
 5518        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 5519    });
 5520    cx.assert_editor_state(indoc! {"
 5521        «TheQuickBrown
 5522        FoxJumpsOver
 5523        TheLazyDogˇ»
 5524    "});
 5525
 5526    // From here on out, test more complex cases of manipulate_text()
 5527
 5528    // Test no selection case - should affect words cursors are in
 5529    // Cursor at beginning, middle, and end of word
 5530    cx.set_state(indoc! {"
 5531        ˇhello big beauˇtiful worldˇ
 5532    "});
 5533    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5534    cx.assert_editor_state(indoc! {"
 5535        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 5536    "});
 5537
 5538    // Test multiple selections on a single line and across multiple lines
 5539    cx.set_state(indoc! {"
 5540        «Theˇ» quick «brown
 5541        foxˇ» jumps «overˇ»
 5542        the «lazyˇ» dog
 5543    "});
 5544    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5545    cx.assert_editor_state(indoc! {"
 5546        «THEˇ» quick «BROWN
 5547        FOXˇ» jumps «OVERˇ»
 5548        the «LAZYˇ» dog
 5549    "});
 5550
 5551    // Test case where text length grows
 5552    cx.set_state(indoc! {"
 5553        «tschüߡ»
 5554    "});
 5555    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5556    cx.assert_editor_state(indoc! {"
 5557        «TSCHÜSSˇ»
 5558    "});
 5559
 5560    // Test to make sure we don't crash when text shrinks
 5561    cx.set_state(indoc! {"
 5562        aaa_bbbˇ
 5563    "});
 5564    cx.update_editor(|e, window, cx| {
 5565        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5566    });
 5567    cx.assert_editor_state(indoc! {"
 5568        «aaaBbbˇ»
 5569    "});
 5570
 5571    // Test to make sure we all aware of the fact that each word can grow and shrink
 5572    // Final selections should be aware of this fact
 5573    cx.set_state(indoc! {"
 5574        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 5575    "});
 5576    cx.update_editor(|e, window, cx| {
 5577        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5578    });
 5579    cx.assert_editor_state(indoc! {"
 5580        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 5581    "});
 5582
 5583    cx.set_state(indoc! {"
 5584        «hElLo, WoRld!ˇ»
 5585    "});
 5586    cx.update_editor(|e, window, cx| {
 5587        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 5588    });
 5589    cx.assert_editor_state(indoc! {"
 5590        «HeLlO, wOrLD!ˇ»
 5591    "});
 5592
 5593    // Test selections with `line_mode() = true`.
 5594    cx.update_editor(|editor, _window, _cx| editor.selections.set_line_mode(true));
 5595    cx.set_state(indoc! {"
 5596        «The quick brown
 5597        fox jumps over
 5598        tˇ»he lazy dog
 5599    "});
 5600    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5601    cx.assert_editor_state(indoc! {"
 5602        «THE QUICK BROWN
 5603        FOX JUMPS OVER
 5604        THE LAZY DOGˇ»
 5605    "});
 5606}
 5607
 5608#[gpui::test]
 5609fn test_duplicate_line(cx: &mut TestAppContext) {
 5610    init_test(cx, |_| {});
 5611
 5612    let editor = cx.add_window(|window, cx| {
 5613        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5614        build_editor(buffer, window, cx)
 5615    });
 5616    _ = editor.update(cx, |editor, window, cx| {
 5617        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5618            s.select_display_ranges([
 5619                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5620                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5621                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5622                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5623            ])
 5624        });
 5625        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5626        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5627        assert_eq!(
 5628            display_ranges(editor, cx),
 5629            vec![
 5630                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 5631                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 5632                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5633                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5634            ]
 5635        );
 5636    });
 5637
 5638    let editor = cx.add_window(|window, cx| {
 5639        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5640        build_editor(buffer, window, cx)
 5641    });
 5642    _ = editor.update(cx, |editor, window, cx| {
 5643        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5644            s.select_display_ranges([
 5645                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5646                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5647            ])
 5648        });
 5649        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5650        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5651        assert_eq!(
 5652            display_ranges(editor, cx),
 5653            vec![
 5654                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 5655                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 5656            ]
 5657        );
 5658    });
 5659
 5660    // With `duplicate_line_up` the selections move to the duplicated lines,
 5661    // which are inserted above the original lines
 5662    let editor = cx.add_window(|window, cx| {
 5663        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5664        build_editor(buffer, window, cx)
 5665    });
 5666    _ = editor.update(cx, |editor, window, cx| {
 5667        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5668            s.select_display_ranges([
 5669                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5670                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5671                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5672                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5673            ])
 5674        });
 5675        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5676        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5677        assert_eq!(
 5678            display_ranges(editor, cx),
 5679            vec![
 5680                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5681                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5682                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 5683                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0),
 5684            ]
 5685        );
 5686    });
 5687
 5688    let editor = cx.add_window(|window, cx| {
 5689        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5690        build_editor(buffer, window, cx)
 5691    });
 5692    _ = editor.update(cx, |editor, window, cx| {
 5693        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5694            s.select_display_ranges([
 5695                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5696                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5697            ])
 5698        });
 5699        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5700        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5701        assert_eq!(
 5702            display_ranges(editor, cx),
 5703            vec![
 5704                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5705                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5706            ]
 5707        );
 5708    });
 5709
 5710    let editor = cx.add_window(|window, cx| {
 5711        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5712        build_editor(buffer, window, cx)
 5713    });
 5714    _ = editor.update(cx, |editor, window, cx| {
 5715        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5716            s.select_display_ranges([
 5717                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5718                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5719            ])
 5720        });
 5721        editor.duplicate_selection(&DuplicateSelection, window, cx);
 5722        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 5723        assert_eq!(
 5724            display_ranges(editor, cx),
 5725            vec![
 5726                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5727                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 5728            ]
 5729        );
 5730    });
 5731}
 5732
 5733#[gpui::test]
 5734fn test_move_line_up_down(cx: &mut TestAppContext) {
 5735    init_test(cx, |_| {});
 5736
 5737    let editor = cx.add_window(|window, cx| {
 5738        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5739        build_editor(buffer, window, cx)
 5740    });
 5741    _ = editor.update(cx, |editor, window, cx| {
 5742        editor.fold_creases(
 5743            vec![
 5744                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5745                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5746                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5747            ],
 5748            true,
 5749            window,
 5750            cx,
 5751        );
 5752        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5753            s.select_display_ranges([
 5754                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5755                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5756                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5757                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 5758            ])
 5759        });
 5760        assert_eq!(
 5761            editor.display_text(cx),
 5762            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 5763        );
 5764
 5765        editor.move_line_up(&MoveLineUp, window, cx);
 5766        assert_eq!(
 5767            editor.display_text(cx),
 5768            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 5769        );
 5770        assert_eq!(
 5771            display_ranges(editor, cx),
 5772            vec![
 5773                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5774                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5775                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5776                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5777            ]
 5778        );
 5779    });
 5780
 5781    _ = editor.update(cx, |editor, window, cx| {
 5782        editor.move_line_down(&MoveLineDown, window, cx);
 5783        assert_eq!(
 5784            editor.display_text(cx),
 5785            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 5786        );
 5787        assert_eq!(
 5788            display_ranges(editor, cx),
 5789            vec![
 5790                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5791                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5792                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5793                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5794            ]
 5795        );
 5796    });
 5797
 5798    _ = editor.update(cx, |editor, window, cx| {
 5799        editor.move_line_down(&MoveLineDown, window, cx);
 5800        assert_eq!(
 5801            editor.display_text(cx),
 5802            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 5803        );
 5804        assert_eq!(
 5805            display_ranges(editor, cx),
 5806            vec![
 5807                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5808                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5809                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5810                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5811            ]
 5812        );
 5813    });
 5814
 5815    _ = editor.update(cx, |editor, window, cx| {
 5816        editor.move_line_up(&MoveLineUp, window, cx);
 5817        assert_eq!(
 5818            editor.display_text(cx),
 5819            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 5820        );
 5821        assert_eq!(
 5822            display_ranges(editor, cx),
 5823            vec![
 5824                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5825                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5826                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5827                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5828            ]
 5829        );
 5830    });
 5831}
 5832
 5833#[gpui::test]
 5834fn test_move_line_up_selection_at_end_of_fold(cx: &mut TestAppContext) {
 5835    init_test(cx, |_| {});
 5836    let editor = cx.add_window(|window, cx| {
 5837        let buffer = MultiBuffer::build_simple("\n\n\n\n\n\naaaa\nbbbb\ncccc", cx);
 5838        build_editor(buffer, window, cx)
 5839    });
 5840    _ = editor.update(cx, |editor, window, cx| {
 5841        editor.fold_creases(
 5842            vec![Crease::simple(
 5843                Point::new(6, 4)..Point::new(7, 4),
 5844                FoldPlaceholder::test(),
 5845            )],
 5846            true,
 5847            window,
 5848            cx,
 5849        );
 5850        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5851            s.select_ranges([Point::new(7, 4)..Point::new(7, 4)])
 5852        });
 5853        assert_eq!(editor.display_text(cx), "\n\n\n\n\n\naaaa⋯\ncccc");
 5854        editor.move_line_up(&MoveLineUp, window, cx);
 5855        let buffer_text = editor.buffer.read(cx).snapshot(cx).text();
 5856        assert_eq!(buffer_text, "\n\n\n\n\naaaa\nbbbb\n\ncccc");
 5857    });
 5858}
 5859
 5860#[gpui::test]
 5861fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 5862    init_test(cx, |_| {});
 5863
 5864    let editor = cx.add_window(|window, cx| {
 5865        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5866        build_editor(buffer, window, cx)
 5867    });
 5868    _ = editor.update(cx, |editor, window, cx| {
 5869        let snapshot = editor.buffer.read(cx).snapshot(cx);
 5870        editor.insert_blocks(
 5871            [BlockProperties {
 5872                style: BlockStyle::Fixed,
 5873                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 5874                height: Some(1),
 5875                render: Arc::new(|_| div().into_any()),
 5876                priority: 0,
 5877            }],
 5878            Some(Autoscroll::fit()),
 5879            cx,
 5880        );
 5881        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5882            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 5883        });
 5884        editor.move_line_down(&MoveLineDown, window, cx);
 5885    });
 5886}
 5887
 5888#[gpui::test]
 5889async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 5890    init_test(cx, |_| {});
 5891
 5892    let mut cx = EditorTestContext::new(cx).await;
 5893    cx.set_state(
 5894        &"
 5895            ˇzero
 5896            one
 5897            two
 5898            three
 5899            four
 5900            five
 5901        "
 5902        .unindent(),
 5903    );
 5904
 5905    // Create a four-line block that replaces three lines of text.
 5906    cx.update_editor(|editor, window, cx| {
 5907        let snapshot = editor.snapshot(window, cx);
 5908        let snapshot = &snapshot.buffer_snapshot();
 5909        let placement = BlockPlacement::Replace(
 5910            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 5911        );
 5912        editor.insert_blocks(
 5913            [BlockProperties {
 5914                placement,
 5915                height: Some(4),
 5916                style: BlockStyle::Sticky,
 5917                render: Arc::new(|_| gpui::div().into_any_element()),
 5918                priority: 0,
 5919            }],
 5920            None,
 5921            cx,
 5922        );
 5923    });
 5924
 5925    // Move down so that the cursor touches the block.
 5926    cx.update_editor(|editor, window, cx| {
 5927        editor.move_down(&Default::default(), window, cx);
 5928    });
 5929    cx.assert_editor_state(
 5930        &"
 5931            zero
 5932            «one
 5933            two
 5934            threeˇ»
 5935            four
 5936            five
 5937        "
 5938        .unindent(),
 5939    );
 5940
 5941    // Move down past the block.
 5942    cx.update_editor(|editor, window, cx| {
 5943        editor.move_down(&Default::default(), window, cx);
 5944    });
 5945    cx.assert_editor_state(
 5946        &"
 5947            zero
 5948            one
 5949            two
 5950            three
 5951            ˇfour
 5952            five
 5953        "
 5954        .unindent(),
 5955    );
 5956}
 5957
 5958#[gpui::test]
 5959fn test_transpose(cx: &mut TestAppContext) {
 5960    init_test(cx, |_| {});
 5961
 5962    _ = cx.add_window(|window, cx| {
 5963        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 5964        editor.set_style(EditorStyle::default(), window, cx);
 5965        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5966            s.select_ranges([1..1])
 5967        });
 5968        editor.transpose(&Default::default(), window, cx);
 5969        assert_eq!(editor.text(cx), "bac");
 5970        assert_eq!(
 5971            editor.selections.ranges(&editor.display_snapshot(cx)),
 5972            [2..2]
 5973        );
 5974
 5975        editor.transpose(&Default::default(), window, cx);
 5976        assert_eq!(editor.text(cx), "bca");
 5977        assert_eq!(
 5978            editor.selections.ranges(&editor.display_snapshot(cx)),
 5979            [3..3]
 5980        );
 5981
 5982        editor.transpose(&Default::default(), window, cx);
 5983        assert_eq!(editor.text(cx), "bac");
 5984        assert_eq!(
 5985            editor.selections.ranges(&editor.display_snapshot(cx)),
 5986            [3..3]
 5987        );
 5988
 5989        editor
 5990    });
 5991
 5992    _ = cx.add_window(|window, cx| {
 5993        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5994        editor.set_style(EditorStyle::default(), window, cx);
 5995        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5996            s.select_ranges([3..3])
 5997        });
 5998        editor.transpose(&Default::default(), window, cx);
 5999        assert_eq!(editor.text(cx), "acb\nde");
 6000        assert_eq!(
 6001            editor.selections.ranges(&editor.display_snapshot(cx)),
 6002            [3..3]
 6003        );
 6004
 6005        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6006            s.select_ranges([4..4])
 6007        });
 6008        editor.transpose(&Default::default(), window, cx);
 6009        assert_eq!(editor.text(cx), "acbd\ne");
 6010        assert_eq!(
 6011            editor.selections.ranges(&editor.display_snapshot(cx)),
 6012            [5..5]
 6013        );
 6014
 6015        editor.transpose(&Default::default(), window, cx);
 6016        assert_eq!(editor.text(cx), "acbde\n");
 6017        assert_eq!(
 6018            editor.selections.ranges(&editor.display_snapshot(cx)),
 6019            [6..6]
 6020        );
 6021
 6022        editor.transpose(&Default::default(), window, cx);
 6023        assert_eq!(editor.text(cx), "acbd\ne");
 6024        assert_eq!(
 6025            editor.selections.ranges(&editor.display_snapshot(cx)),
 6026            [6..6]
 6027        );
 6028
 6029        editor
 6030    });
 6031
 6032    _ = cx.add_window(|window, cx| {
 6033        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 6034        editor.set_style(EditorStyle::default(), window, cx);
 6035        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6036            s.select_ranges([1..1, 2..2, 4..4])
 6037        });
 6038        editor.transpose(&Default::default(), window, cx);
 6039        assert_eq!(editor.text(cx), "bacd\ne");
 6040        assert_eq!(
 6041            editor.selections.ranges(&editor.display_snapshot(cx)),
 6042            [2..2, 3..3, 5..5]
 6043        );
 6044
 6045        editor.transpose(&Default::default(), window, cx);
 6046        assert_eq!(editor.text(cx), "bcade\n");
 6047        assert_eq!(
 6048            editor.selections.ranges(&editor.display_snapshot(cx)),
 6049            [3..3, 4..4, 6..6]
 6050        );
 6051
 6052        editor.transpose(&Default::default(), window, cx);
 6053        assert_eq!(editor.text(cx), "bcda\ne");
 6054        assert_eq!(
 6055            editor.selections.ranges(&editor.display_snapshot(cx)),
 6056            [4..4, 6..6]
 6057        );
 6058
 6059        editor.transpose(&Default::default(), window, cx);
 6060        assert_eq!(editor.text(cx), "bcade\n");
 6061        assert_eq!(
 6062            editor.selections.ranges(&editor.display_snapshot(cx)),
 6063            [4..4, 6..6]
 6064        );
 6065
 6066        editor.transpose(&Default::default(), window, cx);
 6067        assert_eq!(editor.text(cx), "bcaed\n");
 6068        assert_eq!(
 6069            editor.selections.ranges(&editor.display_snapshot(cx)),
 6070            [5..5, 6..6]
 6071        );
 6072
 6073        editor
 6074    });
 6075
 6076    _ = cx.add_window(|window, cx| {
 6077        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 6078        editor.set_style(EditorStyle::default(), window, cx);
 6079        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6080            s.select_ranges([4..4])
 6081        });
 6082        editor.transpose(&Default::default(), window, cx);
 6083        assert_eq!(editor.text(cx), "🏀🍐✋");
 6084        assert_eq!(
 6085            editor.selections.ranges(&editor.display_snapshot(cx)),
 6086            [8..8]
 6087        );
 6088
 6089        editor.transpose(&Default::default(), window, cx);
 6090        assert_eq!(editor.text(cx), "🏀✋🍐");
 6091        assert_eq!(
 6092            editor.selections.ranges(&editor.display_snapshot(cx)),
 6093            [11..11]
 6094        );
 6095
 6096        editor.transpose(&Default::default(), window, cx);
 6097        assert_eq!(editor.text(cx), "🏀🍐✋");
 6098        assert_eq!(
 6099            editor.selections.ranges(&editor.display_snapshot(cx)),
 6100            [11..11]
 6101        );
 6102
 6103        editor
 6104    });
 6105}
 6106
 6107#[gpui::test]
 6108async fn test_rewrap(cx: &mut TestAppContext) {
 6109    init_test(cx, |settings| {
 6110        settings.languages.0.extend([
 6111            (
 6112                "Markdown".into(),
 6113                LanguageSettingsContent {
 6114                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 6115                    preferred_line_length: Some(40),
 6116                    ..Default::default()
 6117                },
 6118            ),
 6119            (
 6120                "Plain Text".into(),
 6121                LanguageSettingsContent {
 6122                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 6123                    preferred_line_length: Some(40),
 6124                    ..Default::default()
 6125                },
 6126            ),
 6127            (
 6128                "C++".into(),
 6129                LanguageSettingsContent {
 6130                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6131                    preferred_line_length: Some(40),
 6132                    ..Default::default()
 6133                },
 6134            ),
 6135            (
 6136                "Python".into(),
 6137                LanguageSettingsContent {
 6138                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6139                    preferred_line_length: Some(40),
 6140                    ..Default::default()
 6141                },
 6142            ),
 6143            (
 6144                "Rust".into(),
 6145                LanguageSettingsContent {
 6146                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6147                    preferred_line_length: Some(40),
 6148                    ..Default::default()
 6149                },
 6150            ),
 6151        ])
 6152    });
 6153
 6154    let mut cx = EditorTestContext::new(cx).await;
 6155
 6156    let cpp_language = Arc::new(Language::new(
 6157        LanguageConfig {
 6158            name: "C++".into(),
 6159            line_comments: vec!["// ".into()],
 6160            ..LanguageConfig::default()
 6161        },
 6162        None,
 6163    ));
 6164    let python_language = Arc::new(Language::new(
 6165        LanguageConfig {
 6166            name: "Python".into(),
 6167            line_comments: vec!["# ".into()],
 6168            ..LanguageConfig::default()
 6169        },
 6170        None,
 6171    ));
 6172    let markdown_language = Arc::new(Language::new(
 6173        LanguageConfig {
 6174            name: "Markdown".into(),
 6175            rewrap_prefixes: vec![
 6176                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 6177                regex::Regex::new("[-*+]\\s+").unwrap(),
 6178            ],
 6179            ..LanguageConfig::default()
 6180        },
 6181        None,
 6182    ));
 6183    let rust_language = Arc::new(
 6184        Language::new(
 6185            LanguageConfig {
 6186                name: "Rust".into(),
 6187                line_comments: vec!["// ".into(), "/// ".into()],
 6188                ..LanguageConfig::default()
 6189            },
 6190            Some(tree_sitter_rust::LANGUAGE.into()),
 6191        )
 6192        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 6193        .unwrap(),
 6194    );
 6195
 6196    let plaintext_language = Arc::new(Language::new(
 6197        LanguageConfig {
 6198            name: "Plain Text".into(),
 6199            ..LanguageConfig::default()
 6200        },
 6201        None,
 6202    ));
 6203
 6204    // Test basic rewrapping of a long line with a cursor
 6205    assert_rewrap(
 6206        indoc! {"
 6207            // ˇThis is a long comment that needs to be wrapped.
 6208        "},
 6209        indoc! {"
 6210            // ˇThis is a long comment that needs to
 6211            // be wrapped.
 6212        "},
 6213        cpp_language.clone(),
 6214        &mut cx,
 6215    );
 6216
 6217    // Test rewrapping a full selection
 6218    assert_rewrap(
 6219        indoc! {"
 6220            «// This selected long comment needs to be wrapped.ˇ»"
 6221        },
 6222        indoc! {"
 6223            «// This selected long comment needs to
 6224            // be wrapped.ˇ»"
 6225        },
 6226        cpp_language.clone(),
 6227        &mut cx,
 6228    );
 6229
 6230    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 6231    assert_rewrap(
 6232        indoc! {"
 6233            // ˇThis is the first line.
 6234            // Thisˇ is the second line.
 6235            // This is the thirdˇ line, all part of one paragraph.
 6236         "},
 6237        indoc! {"
 6238            // ˇThis is the first line. Thisˇ is the
 6239            // second line. This is the thirdˇ line,
 6240            // all part of one paragraph.
 6241         "},
 6242        cpp_language.clone(),
 6243        &mut cx,
 6244    );
 6245
 6246    // Test multiple cursors in different paragraphs trigger separate rewraps
 6247    assert_rewrap(
 6248        indoc! {"
 6249            // ˇThis is the first paragraph, first line.
 6250            // ˇThis is the first paragraph, second line.
 6251
 6252            // ˇThis is the second paragraph, first line.
 6253            // ˇThis is the second paragraph, second line.
 6254        "},
 6255        indoc! {"
 6256            // ˇThis is the first paragraph, first
 6257            // line. ˇThis is the first paragraph,
 6258            // second line.
 6259
 6260            // ˇThis is the second paragraph, first
 6261            // line. ˇThis is the second paragraph,
 6262            // second line.
 6263        "},
 6264        cpp_language.clone(),
 6265        &mut cx,
 6266    );
 6267
 6268    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 6269    assert_rewrap(
 6270        indoc! {"
 6271            «// A regular long long comment to be wrapped.
 6272            /// A documentation long comment to be wrapped.ˇ»
 6273          "},
 6274        indoc! {"
 6275            «// A regular long long comment to be
 6276            // wrapped.
 6277            /// A documentation long comment to be
 6278            /// wrapped.ˇ»
 6279          "},
 6280        rust_language.clone(),
 6281        &mut cx,
 6282    );
 6283
 6284    // Test that change in indentation level trigger seperate rewraps
 6285    assert_rewrap(
 6286        indoc! {"
 6287            fn foo() {
 6288                «// This is a long comment at the base indent.
 6289                    // This is a long comment at the next indent.ˇ»
 6290            }
 6291        "},
 6292        indoc! {"
 6293            fn foo() {
 6294                «// This is a long comment at the
 6295                // base indent.
 6296                    // This is a long comment at the
 6297                    // next indent.ˇ»
 6298            }
 6299        "},
 6300        rust_language.clone(),
 6301        &mut cx,
 6302    );
 6303
 6304    // Test that different comment prefix characters (e.g., '#') are handled correctly
 6305    assert_rewrap(
 6306        indoc! {"
 6307            # ˇThis is a long comment using a pound sign.
 6308        "},
 6309        indoc! {"
 6310            # ˇThis is a long comment using a pound
 6311            # sign.
 6312        "},
 6313        python_language,
 6314        &mut cx,
 6315    );
 6316
 6317    // Test rewrapping only affects comments, not code even when selected
 6318    assert_rewrap(
 6319        indoc! {"
 6320            «/// This doc comment is long and should be wrapped.
 6321            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6322        "},
 6323        indoc! {"
 6324            «/// This doc comment is long and should
 6325            /// be wrapped.
 6326            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6327        "},
 6328        rust_language.clone(),
 6329        &mut cx,
 6330    );
 6331
 6332    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 6333    assert_rewrap(
 6334        indoc! {"
 6335            # Header
 6336
 6337            A long long long line of markdown text to wrap.ˇ
 6338         "},
 6339        indoc! {"
 6340            # Header
 6341
 6342            A long long long line of markdown text
 6343            to wrap.ˇ
 6344         "},
 6345        markdown_language.clone(),
 6346        &mut cx,
 6347    );
 6348
 6349    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 6350    assert_rewrap(
 6351        indoc! {"
 6352            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 6353            2. This is a numbered list item that is very long and needs to be wrapped properly.
 6354            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 6355        "},
 6356        indoc! {"
 6357            «1. This is a numbered list item that is
 6358               very long and needs to be wrapped
 6359               properly.
 6360            2. This is a numbered list item that is
 6361               very long and needs to be wrapped
 6362               properly.
 6363            - This is an unordered list item that is
 6364              also very long and should not merge
 6365              with the numbered item.ˇ»
 6366        "},
 6367        markdown_language.clone(),
 6368        &mut cx,
 6369    );
 6370
 6371    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 6372    assert_rewrap(
 6373        indoc! {"
 6374            «1. This is a numbered list item that is
 6375            very long and needs to be wrapped
 6376            properly.
 6377            2. This is a numbered list item that is
 6378            very long and needs to be wrapped
 6379            properly.
 6380            - This is an unordered list item that is
 6381            also very long and should not merge with
 6382            the numbered item.ˇ»
 6383        "},
 6384        indoc! {"
 6385            «1. This is a numbered list item that is
 6386               very long and needs to be wrapped
 6387               properly.
 6388            2. This is a numbered list item that is
 6389               very long and needs to be wrapped
 6390               properly.
 6391            - This is an unordered list item that is
 6392              also very long and should not merge
 6393              with the numbered item.ˇ»
 6394        "},
 6395        markdown_language.clone(),
 6396        &mut cx,
 6397    );
 6398
 6399    // Test that rewrapping maintain indents even when they already exists.
 6400    assert_rewrap(
 6401        indoc! {"
 6402            «1. This is a numbered list
 6403               item that is very long and needs to be wrapped properly.
 6404            2. This is a numbered list
 6405               item that is very long and needs to be wrapped properly.
 6406            - This is an unordered list item that is also very long and
 6407              should not merge with the numbered item.ˇ»
 6408        "},
 6409        indoc! {"
 6410            «1. This is a numbered list item that is
 6411               very long and needs to be wrapped
 6412               properly.
 6413            2. This is a numbered list item that is
 6414               very long and needs to be wrapped
 6415               properly.
 6416            - This is an unordered list item that is
 6417              also very long and should not merge
 6418              with the numbered item.ˇ»
 6419        "},
 6420        markdown_language,
 6421        &mut cx,
 6422    );
 6423
 6424    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 6425    assert_rewrap(
 6426        indoc! {"
 6427            ˇThis is a very long line of plain text that will be wrapped.
 6428        "},
 6429        indoc! {"
 6430            ˇThis is a very long line of plain text
 6431            that will be wrapped.
 6432        "},
 6433        plaintext_language.clone(),
 6434        &mut cx,
 6435    );
 6436
 6437    // Test that non-commented code acts as a paragraph boundary within a selection
 6438    assert_rewrap(
 6439        indoc! {"
 6440               «// This is the first long comment block to be wrapped.
 6441               fn my_func(a: u32);
 6442               // This is the second long comment block to be wrapped.ˇ»
 6443           "},
 6444        indoc! {"
 6445               «// This is the first long comment block
 6446               // to be wrapped.
 6447               fn my_func(a: u32);
 6448               // This is the second long comment block
 6449               // to be wrapped.ˇ»
 6450           "},
 6451        rust_language,
 6452        &mut cx,
 6453    );
 6454
 6455    // Test rewrapping multiple selections, including ones with blank lines or tabs
 6456    assert_rewrap(
 6457        indoc! {"
 6458            «ˇThis is a very long line that will be wrapped.
 6459
 6460            This is another paragraph in the same selection.»
 6461
 6462            «\tThis is a very long indented line that will be wrapped.ˇ»
 6463         "},
 6464        indoc! {"
 6465            «ˇThis is a very long line that will be
 6466            wrapped.
 6467
 6468            This is another paragraph in the same
 6469            selection.»
 6470
 6471            «\tThis is a very long indented line
 6472            \tthat will be wrapped.ˇ»
 6473         "},
 6474        plaintext_language,
 6475        &mut cx,
 6476    );
 6477
 6478    // Test that an empty comment line acts as a paragraph boundary
 6479    assert_rewrap(
 6480        indoc! {"
 6481            // ˇThis is a long comment that will be wrapped.
 6482            //
 6483            // And this is another long comment that will also be wrapped.ˇ
 6484         "},
 6485        indoc! {"
 6486            // ˇThis is a long comment that will be
 6487            // wrapped.
 6488            //
 6489            // And this is another long comment that
 6490            // will also be wrapped.ˇ
 6491         "},
 6492        cpp_language,
 6493        &mut cx,
 6494    );
 6495
 6496    #[track_caller]
 6497    fn assert_rewrap(
 6498        unwrapped_text: &str,
 6499        wrapped_text: &str,
 6500        language: Arc<Language>,
 6501        cx: &mut EditorTestContext,
 6502    ) {
 6503        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6504        cx.set_state(unwrapped_text);
 6505        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6506        cx.assert_editor_state(wrapped_text);
 6507    }
 6508}
 6509
 6510#[gpui::test]
 6511async fn test_rewrap_block_comments(cx: &mut TestAppContext) {
 6512    init_test(cx, |settings| {
 6513        settings.languages.0.extend([(
 6514            "Rust".into(),
 6515            LanguageSettingsContent {
 6516                allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6517                preferred_line_length: Some(40),
 6518                ..Default::default()
 6519            },
 6520        )])
 6521    });
 6522
 6523    let mut cx = EditorTestContext::new(cx).await;
 6524
 6525    let rust_lang = Arc::new(
 6526        Language::new(
 6527            LanguageConfig {
 6528                name: "Rust".into(),
 6529                line_comments: vec!["// ".into()],
 6530                block_comment: Some(BlockCommentConfig {
 6531                    start: "/*".into(),
 6532                    end: "*/".into(),
 6533                    prefix: "* ".into(),
 6534                    tab_size: 1,
 6535                }),
 6536                documentation_comment: Some(BlockCommentConfig {
 6537                    start: "/**".into(),
 6538                    end: "*/".into(),
 6539                    prefix: "* ".into(),
 6540                    tab_size: 1,
 6541                }),
 6542
 6543                ..LanguageConfig::default()
 6544            },
 6545            Some(tree_sitter_rust::LANGUAGE.into()),
 6546        )
 6547        .with_override_query("[(line_comment) (block_comment)] @comment.inclusive")
 6548        .unwrap(),
 6549    );
 6550
 6551    // regular block comment
 6552    assert_rewrap(
 6553        indoc! {"
 6554            /*
 6555             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6556             */
 6557            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6558        "},
 6559        indoc! {"
 6560            /*
 6561             *ˇ Lorem ipsum dolor sit amet,
 6562             * consectetur adipiscing elit.
 6563             */
 6564            /*
 6565             *ˇ Lorem ipsum dolor sit amet,
 6566             * consectetur adipiscing elit.
 6567             */
 6568        "},
 6569        rust_lang.clone(),
 6570        &mut cx,
 6571    );
 6572
 6573    // indent is respected
 6574    assert_rewrap(
 6575        indoc! {"
 6576            {}
 6577                /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6578        "},
 6579        indoc! {"
 6580            {}
 6581                /*
 6582                 *ˇ Lorem ipsum dolor sit amet,
 6583                 * consectetur adipiscing elit.
 6584                 */
 6585        "},
 6586        rust_lang.clone(),
 6587        &mut cx,
 6588    );
 6589
 6590    // short block comments with inline delimiters
 6591    assert_rewrap(
 6592        indoc! {"
 6593            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6594            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6595             */
 6596            /*
 6597             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6598        "},
 6599        indoc! {"
 6600            /*
 6601             *ˇ Lorem ipsum dolor sit amet,
 6602             * consectetur adipiscing elit.
 6603             */
 6604            /*
 6605             *ˇ Lorem ipsum dolor sit amet,
 6606             * consectetur adipiscing elit.
 6607             */
 6608            /*
 6609             *ˇ Lorem ipsum dolor sit amet,
 6610             * consectetur adipiscing elit.
 6611             */
 6612        "},
 6613        rust_lang.clone(),
 6614        &mut cx,
 6615    );
 6616
 6617    // multiline block comment with inline start/end delimiters
 6618    assert_rewrap(
 6619        indoc! {"
 6620            /*ˇ Lorem ipsum dolor sit amet,
 6621             * consectetur adipiscing elit. */
 6622        "},
 6623        indoc! {"
 6624            /*
 6625             *ˇ Lorem ipsum dolor sit amet,
 6626             * consectetur adipiscing elit.
 6627             */
 6628        "},
 6629        rust_lang.clone(),
 6630        &mut cx,
 6631    );
 6632
 6633    // block comment rewrap still respects paragraph bounds
 6634    assert_rewrap(
 6635        indoc! {"
 6636            /*
 6637             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6638             *
 6639             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6640             */
 6641        "},
 6642        indoc! {"
 6643            /*
 6644             *ˇ Lorem ipsum dolor sit amet,
 6645             * consectetur adipiscing elit.
 6646             *
 6647             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6648             */
 6649        "},
 6650        rust_lang.clone(),
 6651        &mut cx,
 6652    );
 6653
 6654    // documentation comments
 6655    assert_rewrap(
 6656        indoc! {"
 6657            /**ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6658            /**
 6659             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6660             */
 6661        "},
 6662        indoc! {"
 6663            /**
 6664             *ˇ Lorem ipsum dolor sit amet,
 6665             * consectetur adipiscing elit.
 6666             */
 6667            /**
 6668             *ˇ Lorem ipsum dolor sit amet,
 6669             * consectetur adipiscing elit.
 6670             */
 6671        "},
 6672        rust_lang.clone(),
 6673        &mut cx,
 6674    );
 6675
 6676    // different, adjacent comments
 6677    assert_rewrap(
 6678        indoc! {"
 6679            /**
 6680             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6681             */
 6682            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6683            //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6684        "},
 6685        indoc! {"
 6686            /**
 6687             *ˇ Lorem ipsum dolor sit amet,
 6688             * consectetur adipiscing elit.
 6689             */
 6690            /*
 6691             *ˇ Lorem ipsum dolor sit amet,
 6692             * consectetur adipiscing elit.
 6693             */
 6694            //ˇ Lorem ipsum dolor sit amet,
 6695            // consectetur adipiscing elit.
 6696        "},
 6697        rust_lang.clone(),
 6698        &mut cx,
 6699    );
 6700
 6701    // selection w/ single short block comment
 6702    assert_rewrap(
 6703        indoc! {"
 6704            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6705        "},
 6706        indoc! {"
 6707            «/*
 6708             * Lorem ipsum dolor sit amet,
 6709             * consectetur adipiscing elit.
 6710             */ˇ»
 6711        "},
 6712        rust_lang.clone(),
 6713        &mut cx,
 6714    );
 6715
 6716    // rewrapping a single comment w/ abutting comments
 6717    assert_rewrap(
 6718        indoc! {"
 6719            /* ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6720            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6721        "},
 6722        indoc! {"
 6723            /*
 6724             * ˇLorem ipsum dolor sit amet,
 6725             * consectetur adipiscing elit.
 6726             */
 6727            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6728        "},
 6729        rust_lang.clone(),
 6730        &mut cx,
 6731    );
 6732
 6733    // selection w/ non-abutting short block comments
 6734    assert_rewrap(
 6735        indoc! {"
 6736            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6737
 6738            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6739        "},
 6740        indoc! {"
 6741            «/*
 6742             * Lorem ipsum dolor sit amet,
 6743             * consectetur adipiscing elit.
 6744             */
 6745
 6746            /*
 6747             * Lorem ipsum dolor sit amet,
 6748             * consectetur adipiscing elit.
 6749             */ˇ»
 6750        "},
 6751        rust_lang.clone(),
 6752        &mut cx,
 6753    );
 6754
 6755    // selection of multiline block comments
 6756    assert_rewrap(
 6757        indoc! {"
 6758            «/* Lorem ipsum dolor sit amet,
 6759             * consectetur adipiscing elit. */ˇ»
 6760        "},
 6761        indoc! {"
 6762            «/*
 6763             * Lorem ipsum dolor sit amet,
 6764             * consectetur adipiscing elit.
 6765             */ˇ»
 6766        "},
 6767        rust_lang.clone(),
 6768        &mut cx,
 6769    );
 6770
 6771    // partial selection of multiline block comments
 6772    assert_rewrap(
 6773        indoc! {"
 6774            «/* Lorem ipsum dolor sit amet,ˇ»
 6775             * consectetur adipiscing elit. */
 6776            /* Lorem ipsum dolor sit amet,
 6777             «* consectetur adipiscing elit. */ˇ»
 6778        "},
 6779        indoc! {"
 6780            «/*
 6781             * Lorem ipsum dolor sit amet,ˇ»
 6782             * consectetur adipiscing elit. */
 6783            /* Lorem ipsum dolor sit amet,
 6784             «* consectetur adipiscing elit.
 6785             */ˇ»
 6786        "},
 6787        rust_lang.clone(),
 6788        &mut cx,
 6789    );
 6790
 6791    // selection w/ abutting short block comments
 6792    // TODO: should not be combined; should rewrap as 2 comments
 6793    assert_rewrap(
 6794        indoc! {"
 6795            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6796            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6797        "},
 6798        // desired behavior:
 6799        // indoc! {"
 6800        //     «/*
 6801        //      * Lorem ipsum dolor sit amet,
 6802        //      * consectetur adipiscing elit.
 6803        //      */
 6804        //     /*
 6805        //      * Lorem ipsum dolor sit amet,
 6806        //      * consectetur adipiscing elit.
 6807        //      */ˇ»
 6808        // "},
 6809        // actual behaviour:
 6810        indoc! {"
 6811            «/*
 6812             * Lorem ipsum dolor sit amet,
 6813             * consectetur adipiscing elit. Lorem
 6814             * ipsum dolor sit amet, consectetur
 6815             * adipiscing elit.
 6816             */ˇ»
 6817        "},
 6818        rust_lang.clone(),
 6819        &mut cx,
 6820    );
 6821
 6822    // TODO: same as above, but with delimiters on separate line
 6823    // assert_rewrap(
 6824    //     indoc! {"
 6825    //         «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6826    //          */
 6827    //         /*
 6828    //          * Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6829    //     "},
 6830    //     // desired:
 6831    //     // indoc! {"
 6832    //     //     «/*
 6833    //     //      * Lorem ipsum dolor sit amet,
 6834    //     //      * consectetur adipiscing elit.
 6835    //     //      */
 6836    //     //     /*
 6837    //     //      * Lorem ipsum dolor sit amet,
 6838    //     //      * consectetur adipiscing elit.
 6839    //     //      */ˇ»
 6840    //     // "},
 6841    //     // actual: (but with trailing w/s on the empty lines)
 6842    //     indoc! {"
 6843    //         «/*
 6844    //          * Lorem ipsum dolor sit amet,
 6845    //          * consectetur adipiscing elit.
 6846    //          *
 6847    //          */
 6848    //         /*
 6849    //          *
 6850    //          * Lorem ipsum dolor sit amet,
 6851    //          * consectetur adipiscing elit.
 6852    //          */ˇ»
 6853    //     "},
 6854    //     rust_lang.clone(),
 6855    //     &mut cx,
 6856    // );
 6857
 6858    // TODO these are unhandled edge cases; not correct, just documenting known issues
 6859    assert_rewrap(
 6860        indoc! {"
 6861            /*
 6862             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6863             */
 6864            /*
 6865             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6866            /*ˇ Lorem ipsum dolor sit amet */ /* consectetur adipiscing elit. */
 6867        "},
 6868        // desired:
 6869        // indoc! {"
 6870        //     /*
 6871        //      *ˇ Lorem ipsum dolor sit amet,
 6872        //      * consectetur adipiscing elit.
 6873        //      */
 6874        //     /*
 6875        //      *ˇ Lorem ipsum dolor sit amet,
 6876        //      * consectetur adipiscing elit.
 6877        //      */
 6878        //     /*
 6879        //      *ˇ Lorem ipsum dolor sit amet
 6880        //      */ /* consectetur adipiscing elit. */
 6881        // "},
 6882        // actual:
 6883        indoc! {"
 6884            /*
 6885             //ˇ Lorem ipsum dolor sit amet,
 6886             // consectetur adipiscing elit.
 6887             */
 6888            /*
 6889             * //ˇ Lorem ipsum dolor sit amet,
 6890             * consectetur adipiscing elit.
 6891             */
 6892            /*
 6893             *ˇ Lorem ipsum dolor sit amet */ /*
 6894             * consectetur adipiscing elit.
 6895             */
 6896        "},
 6897        rust_lang,
 6898        &mut cx,
 6899    );
 6900
 6901    #[track_caller]
 6902    fn assert_rewrap(
 6903        unwrapped_text: &str,
 6904        wrapped_text: &str,
 6905        language: Arc<Language>,
 6906        cx: &mut EditorTestContext,
 6907    ) {
 6908        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6909        cx.set_state(unwrapped_text);
 6910        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6911        cx.assert_editor_state(wrapped_text);
 6912    }
 6913}
 6914
 6915#[gpui::test]
 6916async fn test_hard_wrap(cx: &mut TestAppContext) {
 6917    init_test(cx, |_| {});
 6918    let mut cx = EditorTestContext::new(cx).await;
 6919
 6920    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 6921    cx.update_editor(|editor, _, cx| {
 6922        editor.set_hard_wrap(Some(14), cx);
 6923    });
 6924
 6925    cx.set_state(indoc!(
 6926        "
 6927        one two three ˇ
 6928        "
 6929    ));
 6930    cx.simulate_input("four");
 6931    cx.run_until_parked();
 6932
 6933    cx.assert_editor_state(indoc!(
 6934        "
 6935        one two three
 6936        fourˇ
 6937        "
 6938    ));
 6939
 6940    cx.update_editor(|editor, window, cx| {
 6941        editor.newline(&Default::default(), window, cx);
 6942    });
 6943    cx.run_until_parked();
 6944    cx.assert_editor_state(indoc!(
 6945        "
 6946        one two three
 6947        four
 6948        ˇ
 6949        "
 6950    ));
 6951
 6952    cx.simulate_input("five");
 6953    cx.run_until_parked();
 6954    cx.assert_editor_state(indoc!(
 6955        "
 6956        one two three
 6957        four
 6958        fiveˇ
 6959        "
 6960    ));
 6961
 6962    cx.update_editor(|editor, window, cx| {
 6963        editor.newline(&Default::default(), window, cx);
 6964    });
 6965    cx.run_until_parked();
 6966    cx.simulate_input("# ");
 6967    cx.run_until_parked();
 6968    cx.assert_editor_state(indoc!(
 6969        "
 6970        one two three
 6971        four
 6972        five
 6973        # ˇ
 6974        "
 6975    ));
 6976
 6977    cx.update_editor(|editor, window, cx| {
 6978        editor.newline(&Default::default(), window, cx);
 6979    });
 6980    cx.run_until_parked();
 6981    cx.assert_editor_state(indoc!(
 6982        "
 6983        one two three
 6984        four
 6985        five
 6986        #\x20
 6987 6988        "
 6989    ));
 6990
 6991    cx.simulate_input(" 6");
 6992    cx.run_until_parked();
 6993    cx.assert_editor_state(indoc!(
 6994        "
 6995        one two three
 6996        four
 6997        five
 6998        #
 6999        # 6ˇ
 7000        "
 7001    ));
 7002}
 7003
 7004#[gpui::test]
 7005async fn test_cut_line_ends(cx: &mut TestAppContext) {
 7006    init_test(cx, |_| {});
 7007
 7008    let mut cx = EditorTestContext::new(cx).await;
 7009
 7010    cx.set_state(indoc! {"The quick brownˇ"});
 7011    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 7012    cx.assert_editor_state(indoc! {"The quick brownˇ"});
 7013
 7014    cx.set_state(indoc! {"The emacs foxˇ"});
 7015    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 7016    cx.assert_editor_state(indoc! {"The emacs foxˇ"});
 7017
 7018    cx.set_state(indoc! {"
 7019        The quick« brownˇ»
 7020        fox jumps overˇ
 7021        the lazy dog"});
 7022    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7023    cx.assert_editor_state(indoc! {"
 7024        The quickˇ
 7025        ˇthe lazy dog"});
 7026
 7027    cx.set_state(indoc! {"
 7028        The quick« brownˇ»
 7029        fox jumps overˇ
 7030        the lazy dog"});
 7031    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 7032    cx.assert_editor_state(indoc! {"
 7033        The quickˇ
 7034        fox jumps overˇthe lazy dog"});
 7035
 7036    cx.set_state(indoc! {"
 7037        The quick« brownˇ»
 7038        fox jumps overˇ
 7039        the lazy dog"});
 7040    cx.update_editor(|e, window, cx| {
 7041        e.cut_to_end_of_line(
 7042            &CutToEndOfLine {
 7043                stop_at_newlines: true,
 7044            },
 7045            window,
 7046            cx,
 7047        )
 7048    });
 7049    cx.assert_editor_state(indoc! {"
 7050        The quickˇ
 7051        fox jumps overˇ
 7052        the lazy dog"});
 7053
 7054    cx.set_state(indoc! {"
 7055        The quick« brownˇ»
 7056        fox jumps overˇ
 7057        the lazy dog"});
 7058    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 7059    cx.assert_editor_state(indoc! {"
 7060        The quickˇ
 7061        fox jumps overˇthe lazy dog"});
 7062}
 7063
 7064#[gpui::test]
 7065async fn test_clipboard(cx: &mut TestAppContext) {
 7066    init_test(cx, |_| {});
 7067
 7068    let mut cx = EditorTestContext::new(cx).await;
 7069
 7070    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 7071    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7072    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 7073
 7074    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 7075    cx.set_state("two ˇfour ˇsix ˇ");
 7076    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7077    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 7078
 7079    // Paste again but with only two cursors. Since the number of cursors doesn't
 7080    // match the number of slices in the clipboard, the entire clipboard text
 7081    // is pasted at each cursor.
 7082    cx.set_state("ˇtwo one✅ four three six five ˇ");
 7083    cx.update_editor(|e, window, cx| {
 7084        e.handle_input("( ", window, cx);
 7085        e.paste(&Paste, window, cx);
 7086        e.handle_input(") ", window, cx);
 7087    });
 7088    cx.assert_editor_state(
 7089        &([
 7090            "( one✅ ",
 7091            "three ",
 7092            "five ) ˇtwo one✅ four three six five ( one✅ ",
 7093            "three ",
 7094            "five ) ˇ",
 7095        ]
 7096        .join("\n")),
 7097    );
 7098
 7099    // Cut with three selections, one of which is full-line.
 7100    cx.set_state(indoc! {"
 7101        1«2ˇ»3
 7102        4ˇ567
 7103        «8ˇ»9"});
 7104    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7105    cx.assert_editor_state(indoc! {"
 7106        1ˇ3
 7107        ˇ9"});
 7108
 7109    // Paste with three selections, noticing how the copied selection that was full-line
 7110    // gets inserted before the second cursor.
 7111    cx.set_state(indoc! {"
 7112        1ˇ3
 7113 7114        «oˇ»ne"});
 7115    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7116    cx.assert_editor_state(indoc! {"
 7117        12ˇ3
 7118        4567
 7119 7120        8ˇne"});
 7121
 7122    // Copy with a single cursor only, which writes the whole line into the clipboard.
 7123    cx.set_state(indoc! {"
 7124        The quick brown
 7125        fox juˇmps over
 7126        the lazy dog"});
 7127    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7128    assert_eq!(
 7129        cx.read_from_clipboard()
 7130            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7131        Some("fox jumps over\n".to_string())
 7132    );
 7133
 7134    // Paste with three selections, noticing how the copied full-line selection is inserted
 7135    // before the empty selections but replaces the selection that is non-empty.
 7136    cx.set_state(indoc! {"
 7137        Tˇhe quick brown
 7138        «foˇ»x jumps over
 7139        tˇhe lazy dog"});
 7140    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7141    cx.assert_editor_state(indoc! {"
 7142        fox jumps over
 7143        Tˇhe quick brown
 7144        fox jumps over
 7145        ˇx jumps over
 7146        fox jumps over
 7147        tˇhe lazy dog"});
 7148}
 7149
 7150#[gpui::test]
 7151async fn test_copy_trim(cx: &mut TestAppContext) {
 7152    init_test(cx, |_| {});
 7153
 7154    let mut cx = EditorTestContext::new(cx).await;
 7155    cx.set_state(
 7156        r#"            «for selection in selections.iter() {
 7157            let mut start = selection.start;
 7158            let mut end = selection.end;
 7159            let is_entire_line = selection.is_empty();
 7160            if is_entire_line {
 7161                start = Point::new(start.row, 0);ˇ»
 7162                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7163            }
 7164        "#,
 7165    );
 7166    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7167    assert_eq!(
 7168        cx.read_from_clipboard()
 7169            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7170        Some(
 7171            "for selection in selections.iter() {
 7172            let mut start = selection.start;
 7173            let mut end = selection.end;
 7174            let is_entire_line = selection.is_empty();
 7175            if is_entire_line {
 7176                start = Point::new(start.row, 0);"
 7177                .to_string()
 7178        ),
 7179        "Regular copying preserves all indentation selected",
 7180    );
 7181    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7182    assert_eq!(
 7183        cx.read_from_clipboard()
 7184            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7185        Some(
 7186            "for selection in selections.iter() {
 7187let mut start = selection.start;
 7188let mut end = selection.end;
 7189let is_entire_line = selection.is_empty();
 7190if is_entire_line {
 7191    start = Point::new(start.row, 0);"
 7192                .to_string()
 7193        ),
 7194        "Copying with stripping should strip all leading whitespaces"
 7195    );
 7196
 7197    cx.set_state(
 7198        r#"       «     for selection in selections.iter() {
 7199            let mut start = selection.start;
 7200            let mut end = selection.end;
 7201            let is_entire_line = selection.is_empty();
 7202            if is_entire_line {
 7203                start = Point::new(start.row, 0);ˇ»
 7204                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7205            }
 7206        "#,
 7207    );
 7208    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7209    assert_eq!(
 7210        cx.read_from_clipboard()
 7211            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7212        Some(
 7213            "     for selection in selections.iter() {
 7214            let mut start = selection.start;
 7215            let mut end = selection.end;
 7216            let is_entire_line = selection.is_empty();
 7217            if is_entire_line {
 7218                start = Point::new(start.row, 0);"
 7219                .to_string()
 7220        ),
 7221        "Regular copying preserves all indentation selected",
 7222    );
 7223    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7224    assert_eq!(
 7225        cx.read_from_clipboard()
 7226            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7227        Some(
 7228            "for selection in selections.iter() {
 7229let mut start = selection.start;
 7230let mut end = selection.end;
 7231let is_entire_line = selection.is_empty();
 7232if is_entire_line {
 7233    start = Point::new(start.row, 0);"
 7234                .to_string()
 7235        ),
 7236        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 7237    );
 7238
 7239    cx.set_state(
 7240        r#"       «ˇ     for selection in selections.iter() {
 7241            let mut start = selection.start;
 7242            let mut end = selection.end;
 7243            let is_entire_line = selection.is_empty();
 7244            if is_entire_line {
 7245                start = Point::new(start.row, 0);»
 7246                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7247            }
 7248        "#,
 7249    );
 7250    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7251    assert_eq!(
 7252        cx.read_from_clipboard()
 7253            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7254        Some(
 7255            "     for selection in selections.iter() {
 7256            let mut start = selection.start;
 7257            let mut end = selection.end;
 7258            let is_entire_line = selection.is_empty();
 7259            if is_entire_line {
 7260                start = Point::new(start.row, 0);"
 7261                .to_string()
 7262        ),
 7263        "Regular copying for reverse selection works the same",
 7264    );
 7265    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7266    assert_eq!(
 7267        cx.read_from_clipboard()
 7268            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7269        Some(
 7270            "for selection in selections.iter() {
 7271let mut start = selection.start;
 7272let mut end = selection.end;
 7273let is_entire_line = selection.is_empty();
 7274if is_entire_line {
 7275    start = Point::new(start.row, 0);"
 7276                .to_string()
 7277        ),
 7278        "Copying with stripping for reverse selection works the same"
 7279    );
 7280
 7281    cx.set_state(
 7282        r#"            for selection «in selections.iter() {
 7283            let mut start = selection.start;
 7284            let mut end = selection.end;
 7285            let is_entire_line = selection.is_empty();
 7286            if is_entire_line {
 7287                start = Point::new(start.row, 0);ˇ»
 7288                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7289            }
 7290        "#,
 7291    );
 7292    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7293    assert_eq!(
 7294        cx.read_from_clipboard()
 7295            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7296        Some(
 7297            "in selections.iter() {
 7298            let mut start = selection.start;
 7299            let mut end = selection.end;
 7300            let is_entire_line = selection.is_empty();
 7301            if is_entire_line {
 7302                start = Point::new(start.row, 0);"
 7303                .to_string()
 7304        ),
 7305        "When selecting past the indent, the copying works as usual",
 7306    );
 7307    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7308    assert_eq!(
 7309        cx.read_from_clipboard()
 7310            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7311        Some(
 7312            "in selections.iter() {
 7313            let mut start = selection.start;
 7314            let mut end = selection.end;
 7315            let is_entire_line = selection.is_empty();
 7316            if is_entire_line {
 7317                start = Point::new(start.row, 0);"
 7318                .to_string()
 7319        ),
 7320        "When selecting past the indent, nothing is trimmed"
 7321    );
 7322
 7323    cx.set_state(
 7324        r#"            «for selection in selections.iter() {
 7325            let mut start = selection.start;
 7326
 7327            let mut end = selection.end;
 7328            let is_entire_line = selection.is_empty();
 7329            if is_entire_line {
 7330                start = Point::new(start.row, 0);
 7331ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7332            }
 7333        "#,
 7334    );
 7335    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7336    assert_eq!(
 7337        cx.read_from_clipboard()
 7338            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7339        Some(
 7340            "for selection in selections.iter() {
 7341let mut start = selection.start;
 7342
 7343let mut end = selection.end;
 7344let is_entire_line = selection.is_empty();
 7345if is_entire_line {
 7346    start = Point::new(start.row, 0);
 7347"
 7348            .to_string()
 7349        ),
 7350        "Copying with stripping should ignore empty lines"
 7351    );
 7352}
 7353
 7354#[gpui::test]
 7355async fn test_paste_multiline(cx: &mut TestAppContext) {
 7356    init_test(cx, |_| {});
 7357
 7358    let mut cx = EditorTestContext::new(cx).await;
 7359    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7360
 7361    // Cut an indented block, without the leading whitespace.
 7362    cx.set_state(indoc! {"
 7363        const a: B = (
 7364            c(),
 7365            «d(
 7366                e,
 7367                f
 7368            )ˇ»
 7369        );
 7370    "});
 7371    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7372    cx.assert_editor_state(indoc! {"
 7373        const a: B = (
 7374            c(),
 7375            ˇ
 7376        );
 7377    "});
 7378
 7379    // Paste it at the same position.
 7380    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7381    cx.assert_editor_state(indoc! {"
 7382        const a: B = (
 7383            c(),
 7384            d(
 7385                e,
 7386                f
 7387 7388        );
 7389    "});
 7390
 7391    // Paste it at a line with a lower indent level.
 7392    cx.set_state(indoc! {"
 7393        ˇ
 7394        const a: B = (
 7395            c(),
 7396        );
 7397    "});
 7398    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7399    cx.assert_editor_state(indoc! {"
 7400        d(
 7401            e,
 7402            f
 7403 7404        const a: B = (
 7405            c(),
 7406        );
 7407    "});
 7408
 7409    // Cut an indented block, with the leading whitespace.
 7410    cx.set_state(indoc! {"
 7411        const a: B = (
 7412            c(),
 7413        «    d(
 7414                e,
 7415                f
 7416            )
 7417        ˇ»);
 7418    "});
 7419    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7420    cx.assert_editor_state(indoc! {"
 7421        const a: B = (
 7422            c(),
 7423        ˇ);
 7424    "});
 7425
 7426    // Paste it at the same position.
 7427    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7428    cx.assert_editor_state(indoc! {"
 7429        const a: B = (
 7430            c(),
 7431            d(
 7432                e,
 7433                f
 7434            )
 7435        ˇ);
 7436    "});
 7437
 7438    // Paste it at a line with a higher indent level.
 7439    cx.set_state(indoc! {"
 7440        const a: B = (
 7441            c(),
 7442            d(
 7443                e,
 7444 7445            )
 7446        );
 7447    "});
 7448    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7449    cx.assert_editor_state(indoc! {"
 7450        const a: B = (
 7451            c(),
 7452            d(
 7453                e,
 7454                f    d(
 7455                    e,
 7456                    f
 7457                )
 7458        ˇ
 7459            )
 7460        );
 7461    "});
 7462
 7463    // Copy an indented block, starting mid-line
 7464    cx.set_state(indoc! {"
 7465        const a: B = (
 7466            c(),
 7467            somethin«g(
 7468                e,
 7469                f
 7470            )ˇ»
 7471        );
 7472    "});
 7473    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7474
 7475    // Paste it on a line with a lower indent level
 7476    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 7477    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7478    cx.assert_editor_state(indoc! {"
 7479        const a: B = (
 7480            c(),
 7481            something(
 7482                e,
 7483                f
 7484            )
 7485        );
 7486        g(
 7487            e,
 7488            f
 7489"});
 7490}
 7491
 7492#[gpui::test]
 7493async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 7494    init_test(cx, |_| {});
 7495
 7496    cx.write_to_clipboard(ClipboardItem::new_string(
 7497        "    d(\n        e\n    );\n".into(),
 7498    ));
 7499
 7500    let mut cx = EditorTestContext::new(cx).await;
 7501    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7502
 7503    cx.set_state(indoc! {"
 7504        fn a() {
 7505            b();
 7506            if c() {
 7507                ˇ
 7508            }
 7509        }
 7510    "});
 7511
 7512    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7513    cx.assert_editor_state(indoc! {"
 7514        fn a() {
 7515            b();
 7516            if c() {
 7517                d(
 7518                    e
 7519                );
 7520        ˇ
 7521            }
 7522        }
 7523    "});
 7524
 7525    cx.set_state(indoc! {"
 7526        fn a() {
 7527            b();
 7528            ˇ
 7529        }
 7530    "});
 7531
 7532    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7533    cx.assert_editor_state(indoc! {"
 7534        fn a() {
 7535            b();
 7536            d(
 7537                e
 7538            );
 7539        ˇ
 7540        }
 7541    "});
 7542}
 7543
 7544#[gpui::test]
 7545fn test_select_all(cx: &mut TestAppContext) {
 7546    init_test(cx, |_| {});
 7547
 7548    let editor = cx.add_window(|window, cx| {
 7549        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 7550        build_editor(buffer, window, cx)
 7551    });
 7552    _ = editor.update(cx, |editor, window, cx| {
 7553        editor.select_all(&SelectAll, window, cx);
 7554        assert_eq!(
 7555            display_ranges(editor, cx),
 7556            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 7557        );
 7558    });
 7559}
 7560
 7561#[gpui::test]
 7562fn test_select_line(cx: &mut TestAppContext) {
 7563    init_test(cx, |_| {});
 7564
 7565    let editor = cx.add_window(|window, cx| {
 7566        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 7567        build_editor(buffer, window, cx)
 7568    });
 7569    _ = editor.update(cx, |editor, window, cx| {
 7570        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7571            s.select_display_ranges([
 7572                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7573                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7574                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7575                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 7576            ])
 7577        });
 7578        editor.select_line(&SelectLine, window, cx);
 7579        assert_eq!(
 7580            display_ranges(editor, cx),
 7581            vec![
 7582                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 7583                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 7584            ]
 7585        );
 7586    });
 7587
 7588    _ = editor.update(cx, |editor, window, cx| {
 7589        editor.select_line(&SelectLine, window, cx);
 7590        assert_eq!(
 7591            display_ranges(editor, cx),
 7592            vec![
 7593                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 7594                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 7595            ]
 7596        );
 7597    });
 7598
 7599    _ = editor.update(cx, |editor, window, cx| {
 7600        editor.select_line(&SelectLine, window, cx);
 7601        assert_eq!(
 7602            display_ranges(editor, cx),
 7603            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 7604        );
 7605    });
 7606}
 7607
 7608#[gpui::test]
 7609async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 7610    init_test(cx, |_| {});
 7611    let mut cx = EditorTestContext::new(cx).await;
 7612
 7613    #[track_caller]
 7614    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 7615        cx.set_state(initial_state);
 7616        cx.update_editor(|e, window, cx| {
 7617            e.split_selection_into_lines(&Default::default(), window, cx)
 7618        });
 7619        cx.assert_editor_state(expected_state);
 7620    }
 7621
 7622    // Selection starts and ends at the middle of lines, left-to-right
 7623    test(
 7624        &mut cx,
 7625        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 7626        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7627    );
 7628    // Same thing, right-to-left
 7629    test(
 7630        &mut cx,
 7631        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 7632        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7633    );
 7634
 7635    // Whole buffer, left-to-right, last line *doesn't* end with newline
 7636    test(
 7637        &mut cx,
 7638        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 7639        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7640    );
 7641    // Same thing, right-to-left
 7642    test(
 7643        &mut cx,
 7644        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 7645        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7646    );
 7647
 7648    // Whole buffer, left-to-right, last line ends with newline
 7649    test(
 7650        &mut cx,
 7651        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 7652        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7653    );
 7654    // Same thing, right-to-left
 7655    test(
 7656        &mut cx,
 7657        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 7658        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7659    );
 7660
 7661    // Starts at the end of a line, ends at the start of another
 7662    test(
 7663        &mut cx,
 7664        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 7665        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 7666    );
 7667}
 7668
 7669#[gpui::test]
 7670async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 7671    init_test(cx, |_| {});
 7672
 7673    let editor = cx.add_window(|window, cx| {
 7674        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 7675        build_editor(buffer, window, cx)
 7676    });
 7677
 7678    // setup
 7679    _ = editor.update(cx, |editor, window, cx| {
 7680        editor.fold_creases(
 7681            vec![
 7682                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 7683                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 7684                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 7685            ],
 7686            true,
 7687            window,
 7688            cx,
 7689        );
 7690        assert_eq!(
 7691            editor.display_text(cx),
 7692            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7693        );
 7694    });
 7695
 7696    _ = editor.update(cx, |editor, window, cx| {
 7697        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7698            s.select_display_ranges([
 7699                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7700                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7701                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7702                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 7703            ])
 7704        });
 7705        editor.split_selection_into_lines(&Default::default(), window, cx);
 7706        assert_eq!(
 7707            editor.display_text(cx),
 7708            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7709        );
 7710    });
 7711    EditorTestContext::for_editor(editor, cx)
 7712        .await
 7713        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 7714
 7715    _ = editor.update(cx, |editor, window, cx| {
 7716        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7717            s.select_display_ranges([
 7718                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 7719            ])
 7720        });
 7721        editor.split_selection_into_lines(&Default::default(), window, cx);
 7722        assert_eq!(
 7723            editor.display_text(cx),
 7724            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 7725        );
 7726        assert_eq!(
 7727            display_ranges(editor, cx),
 7728            [
 7729                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 7730                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 7731                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 7732                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 7733                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 7734                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 7735                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 7736            ]
 7737        );
 7738    });
 7739    EditorTestContext::for_editor(editor, cx)
 7740        .await
 7741        .assert_editor_state(
 7742            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 7743        );
 7744}
 7745
 7746#[gpui::test]
 7747async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 7748    init_test(cx, |_| {});
 7749
 7750    let mut cx = EditorTestContext::new(cx).await;
 7751
 7752    cx.set_state(indoc!(
 7753        r#"abc
 7754           defˇghi
 7755
 7756           jk
 7757           nlmo
 7758           "#
 7759    ));
 7760
 7761    cx.update_editor(|editor, window, cx| {
 7762        editor.add_selection_above(&Default::default(), window, cx);
 7763    });
 7764
 7765    cx.assert_editor_state(indoc!(
 7766        r#"abcˇ
 7767           defˇghi
 7768
 7769           jk
 7770           nlmo
 7771           "#
 7772    ));
 7773
 7774    cx.update_editor(|editor, window, cx| {
 7775        editor.add_selection_above(&Default::default(), window, cx);
 7776    });
 7777
 7778    cx.assert_editor_state(indoc!(
 7779        r#"abcˇ
 7780            defˇghi
 7781
 7782            jk
 7783            nlmo
 7784            "#
 7785    ));
 7786
 7787    cx.update_editor(|editor, window, cx| {
 7788        editor.add_selection_below(&Default::default(), window, cx);
 7789    });
 7790
 7791    cx.assert_editor_state(indoc!(
 7792        r#"abc
 7793           defˇghi
 7794
 7795           jk
 7796           nlmo
 7797           "#
 7798    ));
 7799
 7800    cx.update_editor(|editor, window, cx| {
 7801        editor.undo_selection(&Default::default(), window, cx);
 7802    });
 7803
 7804    cx.assert_editor_state(indoc!(
 7805        r#"abcˇ
 7806           defˇghi
 7807
 7808           jk
 7809           nlmo
 7810           "#
 7811    ));
 7812
 7813    cx.update_editor(|editor, window, cx| {
 7814        editor.redo_selection(&Default::default(), window, cx);
 7815    });
 7816
 7817    cx.assert_editor_state(indoc!(
 7818        r#"abc
 7819           defˇghi
 7820
 7821           jk
 7822           nlmo
 7823           "#
 7824    ));
 7825
 7826    cx.update_editor(|editor, window, cx| {
 7827        editor.add_selection_below(&Default::default(), window, cx);
 7828    });
 7829
 7830    cx.assert_editor_state(indoc!(
 7831        r#"abc
 7832           defˇghi
 7833           ˇ
 7834           jk
 7835           nlmo
 7836           "#
 7837    ));
 7838
 7839    cx.update_editor(|editor, window, cx| {
 7840        editor.add_selection_below(&Default::default(), window, cx);
 7841    });
 7842
 7843    cx.assert_editor_state(indoc!(
 7844        r#"abc
 7845           defˇghi
 7846           ˇ
 7847           jkˇ
 7848           nlmo
 7849           "#
 7850    ));
 7851
 7852    cx.update_editor(|editor, window, cx| {
 7853        editor.add_selection_below(&Default::default(), window, cx);
 7854    });
 7855
 7856    cx.assert_editor_state(indoc!(
 7857        r#"abc
 7858           defˇghi
 7859           ˇ
 7860           jkˇ
 7861           nlmˇo
 7862           "#
 7863    ));
 7864
 7865    cx.update_editor(|editor, window, cx| {
 7866        editor.add_selection_below(&Default::default(), window, cx);
 7867    });
 7868
 7869    cx.assert_editor_state(indoc!(
 7870        r#"abc
 7871           defˇghi
 7872           ˇ
 7873           jkˇ
 7874           nlmˇo
 7875           ˇ"#
 7876    ));
 7877
 7878    // change selections
 7879    cx.set_state(indoc!(
 7880        r#"abc
 7881           def«ˇg»hi
 7882
 7883           jk
 7884           nlmo
 7885           "#
 7886    ));
 7887
 7888    cx.update_editor(|editor, window, cx| {
 7889        editor.add_selection_below(&Default::default(), window, cx);
 7890    });
 7891
 7892    cx.assert_editor_state(indoc!(
 7893        r#"abc
 7894           def«ˇg»hi
 7895
 7896           jk
 7897           nlm«ˇo»
 7898           "#
 7899    ));
 7900
 7901    cx.update_editor(|editor, window, cx| {
 7902        editor.add_selection_below(&Default::default(), window, cx);
 7903    });
 7904
 7905    cx.assert_editor_state(indoc!(
 7906        r#"abc
 7907           def«ˇg»hi
 7908
 7909           jk
 7910           nlm«ˇo»
 7911           "#
 7912    ));
 7913
 7914    cx.update_editor(|editor, window, cx| {
 7915        editor.add_selection_above(&Default::default(), window, cx);
 7916    });
 7917
 7918    cx.assert_editor_state(indoc!(
 7919        r#"abc
 7920           def«ˇg»hi
 7921
 7922           jk
 7923           nlmo
 7924           "#
 7925    ));
 7926
 7927    cx.update_editor(|editor, window, cx| {
 7928        editor.add_selection_above(&Default::default(), window, cx);
 7929    });
 7930
 7931    cx.assert_editor_state(indoc!(
 7932        r#"abc
 7933           def«ˇg»hi
 7934
 7935           jk
 7936           nlmo
 7937           "#
 7938    ));
 7939
 7940    // Change selections again
 7941    cx.set_state(indoc!(
 7942        r#"a«bc
 7943           defgˇ»hi
 7944
 7945           jk
 7946           nlmo
 7947           "#
 7948    ));
 7949
 7950    cx.update_editor(|editor, window, cx| {
 7951        editor.add_selection_below(&Default::default(), window, cx);
 7952    });
 7953
 7954    cx.assert_editor_state(indoc!(
 7955        r#"a«bcˇ»
 7956           d«efgˇ»hi
 7957
 7958           j«kˇ»
 7959           nlmo
 7960           "#
 7961    ));
 7962
 7963    cx.update_editor(|editor, window, cx| {
 7964        editor.add_selection_below(&Default::default(), window, cx);
 7965    });
 7966    cx.assert_editor_state(indoc!(
 7967        r#"a«bcˇ»
 7968           d«efgˇ»hi
 7969
 7970           j«kˇ»
 7971           n«lmoˇ»
 7972           "#
 7973    ));
 7974    cx.update_editor(|editor, window, cx| {
 7975        editor.add_selection_above(&Default::default(), window, cx);
 7976    });
 7977
 7978    cx.assert_editor_state(indoc!(
 7979        r#"a«bcˇ»
 7980           d«efgˇ»hi
 7981
 7982           j«kˇ»
 7983           nlmo
 7984           "#
 7985    ));
 7986
 7987    // Change selections again
 7988    cx.set_state(indoc!(
 7989        r#"abc
 7990           d«ˇefghi
 7991
 7992           jk
 7993           nlm»o
 7994           "#
 7995    ));
 7996
 7997    cx.update_editor(|editor, window, cx| {
 7998        editor.add_selection_above(&Default::default(), window, cx);
 7999    });
 8000
 8001    cx.assert_editor_state(indoc!(
 8002        r#"a«ˇbc»
 8003           d«ˇef»ghi
 8004
 8005           j«ˇk»
 8006           n«ˇlm»o
 8007           "#
 8008    ));
 8009
 8010    cx.update_editor(|editor, window, cx| {
 8011        editor.add_selection_below(&Default::default(), window, cx);
 8012    });
 8013
 8014    cx.assert_editor_state(indoc!(
 8015        r#"abc
 8016           d«ˇef»ghi
 8017
 8018           j«ˇk»
 8019           n«ˇlm»o
 8020           "#
 8021    ));
 8022}
 8023
 8024#[gpui::test]
 8025async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 8026    init_test(cx, |_| {});
 8027    let mut cx = EditorTestContext::new(cx).await;
 8028
 8029    cx.set_state(indoc!(
 8030        r#"line onˇe
 8031           liˇne two
 8032           line three
 8033           line four"#
 8034    ));
 8035
 8036    cx.update_editor(|editor, window, cx| {
 8037        editor.add_selection_below(&Default::default(), window, cx);
 8038    });
 8039
 8040    // test multiple cursors expand in the same direction
 8041    cx.assert_editor_state(indoc!(
 8042        r#"line onˇe
 8043           liˇne twˇo
 8044           liˇne three
 8045           line four"#
 8046    ));
 8047
 8048    cx.update_editor(|editor, window, cx| {
 8049        editor.add_selection_below(&Default::default(), window, cx);
 8050    });
 8051
 8052    cx.update_editor(|editor, window, cx| {
 8053        editor.add_selection_below(&Default::default(), window, cx);
 8054    });
 8055
 8056    // test multiple cursors expand below overflow
 8057    cx.assert_editor_state(indoc!(
 8058        r#"line onˇe
 8059           liˇne twˇo
 8060           liˇne thˇree
 8061           liˇne foˇur"#
 8062    ));
 8063
 8064    cx.update_editor(|editor, window, cx| {
 8065        editor.add_selection_above(&Default::default(), window, cx);
 8066    });
 8067
 8068    // test multiple cursors retrieves back correctly
 8069    cx.assert_editor_state(indoc!(
 8070        r#"line onˇe
 8071           liˇne twˇo
 8072           liˇne thˇree
 8073           line four"#
 8074    ));
 8075
 8076    cx.update_editor(|editor, window, cx| {
 8077        editor.add_selection_above(&Default::default(), window, cx);
 8078    });
 8079
 8080    cx.update_editor(|editor, window, cx| {
 8081        editor.add_selection_above(&Default::default(), window, cx);
 8082    });
 8083
 8084    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 8085    cx.assert_editor_state(indoc!(
 8086        r#"liˇne onˇe
 8087           liˇne two
 8088           line three
 8089           line four"#
 8090    ));
 8091
 8092    cx.update_editor(|editor, window, cx| {
 8093        editor.undo_selection(&Default::default(), window, cx);
 8094    });
 8095
 8096    // test undo
 8097    cx.assert_editor_state(indoc!(
 8098        r#"line onˇe
 8099           liˇne twˇo
 8100           line three
 8101           line four"#
 8102    ));
 8103
 8104    cx.update_editor(|editor, window, cx| {
 8105        editor.redo_selection(&Default::default(), window, cx);
 8106    });
 8107
 8108    // test redo
 8109    cx.assert_editor_state(indoc!(
 8110        r#"liˇne onˇe
 8111           liˇne two
 8112           line three
 8113           line four"#
 8114    ));
 8115
 8116    cx.set_state(indoc!(
 8117        r#"abcd
 8118           ef«ghˇ»
 8119           ijkl
 8120           «mˇ»nop"#
 8121    ));
 8122
 8123    cx.update_editor(|editor, window, cx| {
 8124        editor.add_selection_above(&Default::default(), window, cx);
 8125    });
 8126
 8127    // test multiple selections expand in the same direction
 8128    cx.assert_editor_state(indoc!(
 8129        r#"ab«cdˇ»
 8130           ef«ghˇ»
 8131           «iˇ»jkl
 8132           «mˇ»nop"#
 8133    ));
 8134
 8135    cx.update_editor(|editor, window, cx| {
 8136        editor.add_selection_above(&Default::default(), window, cx);
 8137    });
 8138
 8139    // test multiple selection upward overflow
 8140    cx.assert_editor_state(indoc!(
 8141        r#"ab«cdˇ»
 8142           «eˇ»f«ghˇ»
 8143           «iˇ»jkl
 8144           «mˇ»nop"#
 8145    ));
 8146
 8147    cx.update_editor(|editor, window, cx| {
 8148        editor.add_selection_below(&Default::default(), window, cx);
 8149    });
 8150
 8151    // test multiple selection retrieves back correctly
 8152    cx.assert_editor_state(indoc!(
 8153        r#"abcd
 8154           ef«ghˇ»
 8155           «iˇ»jkl
 8156           «mˇ»nop"#
 8157    ));
 8158
 8159    cx.update_editor(|editor, window, cx| {
 8160        editor.add_selection_below(&Default::default(), window, cx);
 8161    });
 8162
 8163    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 8164    cx.assert_editor_state(indoc!(
 8165        r#"abcd
 8166           ef«ghˇ»
 8167           ij«klˇ»
 8168           «mˇ»nop"#
 8169    ));
 8170
 8171    cx.update_editor(|editor, window, cx| {
 8172        editor.undo_selection(&Default::default(), window, cx);
 8173    });
 8174
 8175    // test undo
 8176    cx.assert_editor_state(indoc!(
 8177        r#"abcd
 8178           ef«ghˇ»
 8179           «iˇ»jkl
 8180           «mˇ»nop"#
 8181    ));
 8182
 8183    cx.update_editor(|editor, window, cx| {
 8184        editor.redo_selection(&Default::default(), window, cx);
 8185    });
 8186
 8187    // test redo
 8188    cx.assert_editor_state(indoc!(
 8189        r#"abcd
 8190           ef«ghˇ»
 8191           ij«klˇ»
 8192           «mˇ»nop"#
 8193    ));
 8194}
 8195
 8196#[gpui::test]
 8197async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 8198    init_test(cx, |_| {});
 8199    let mut cx = EditorTestContext::new(cx).await;
 8200
 8201    cx.set_state(indoc!(
 8202        r#"line onˇe
 8203           liˇne two
 8204           line three
 8205           line four"#
 8206    ));
 8207
 8208    cx.update_editor(|editor, window, cx| {
 8209        editor.add_selection_below(&Default::default(), window, cx);
 8210        editor.add_selection_below(&Default::default(), window, cx);
 8211        editor.add_selection_below(&Default::default(), window, cx);
 8212    });
 8213
 8214    // initial state with two multi cursor groups
 8215    cx.assert_editor_state(indoc!(
 8216        r#"line onˇe
 8217           liˇne twˇo
 8218           liˇne thˇree
 8219           liˇne foˇur"#
 8220    ));
 8221
 8222    // add single cursor in middle - simulate opt click
 8223    cx.update_editor(|editor, window, cx| {
 8224        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 8225        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8226        editor.end_selection(window, cx);
 8227    });
 8228
 8229    cx.assert_editor_state(indoc!(
 8230        r#"line onˇe
 8231           liˇne twˇo
 8232           liˇneˇ thˇree
 8233           liˇne foˇur"#
 8234    ));
 8235
 8236    cx.update_editor(|editor, window, cx| {
 8237        editor.add_selection_above(&Default::default(), window, cx);
 8238    });
 8239
 8240    // test new added selection expands above and existing selection shrinks
 8241    cx.assert_editor_state(indoc!(
 8242        r#"line onˇe
 8243           liˇneˇ twˇo
 8244           liˇneˇ thˇree
 8245           line four"#
 8246    ));
 8247
 8248    cx.update_editor(|editor, window, cx| {
 8249        editor.add_selection_above(&Default::default(), window, cx);
 8250    });
 8251
 8252    // test new added selection expands above and existing selection shrinks
 8253    cx.assert_editor_state(indoc!(
 8254        r#"lineˇ onˇe
 8255           liˇneˇ twˇo
 8256           lineˇ three
 8257           line four"#
 8258    ));
 8259
 8260    // intial state with two selection groups
 8261    cx.set_state(indoc!(
 8262        r#"abcd
 8263           ef«ghˇ»
 8264           ijkl
 8265           «mˇ»nop"#
 8266    ));
 8267
 8268    cx.update_editor(|editor, window, cx| {
 8269        editor.add_selection_above(&Default::default(), window, cx);
 8270        editor.add_selection_above(&Default::default(), window, cx);
 8271    });
 8272
 8273    cx.assert_editor_state(indoc!(
 8274        r#"ab«cdˇ»
 8275           «eˇ»f«ghˇ»
 8276           «iˇ»jkl
 8277           «mˇ»nop"#
 8278    ));
 8279
 8280    // add single selection in middle - simulate opt drag
 8281    cx.update_editor(|editor, window, cx| {
 8282        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 8283        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8284        editor.update_selection(
 8285            DisplayPoint::new(DisplayRow(2), 4),
 8286            0,
 8287            gpui::Point::<f32>::default(),
 8288            window,
 8289            cx,
 8290        );
 8291        editor.end_selection(window, cx);
 8292    });
 8293
 8294    cx.assert_editor_state(indoc!(
 8295        r#"ab«cdˇ»
 8296           «eˇ»f«ghˇ»
 8297           «iˇ»jk«lˇ»
 8298           «mˇ»nop"#
 8299    ));
 8300
 8301    cx.update_editor(|editor, window, cx| {
 8302        editor.add_selection_below(&Default::default(), window, cx);
 8303    });
 8304
 8305    // test new added selection expands below, others shrinks from above
 8306    cx.assert_editor_state(indoc!(
 8307        r#"abcd
 8308           ef«ghˇ»
 8309           «iˇ»jk«lˇ»
 8310           «mˇ»no«pˇ»"#
 8311    ));
 8312}
 8313
 8314#[gpui::test]
 8315async fn test_select_next(cx: &mut TestAppContext) {
 8316    init_test(cx, |_| {});
 8317
 8318    let mut cx = EditorTestContext::new(cx).await;
 8319    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8320
 8321    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8322        .unwrap();
 8323    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8324
 8325    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8326        .unwrap();
 8327    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8328
 8329    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8330    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8331
 8332    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8333    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8334
 8335    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8336        .unwrap();
 8337    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8338
 8339    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8340        .unwrap();
 8341    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8342
 8343    // Test selection direction should be preserved
 8344    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8345
 8346    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8347        .unwrap();
 8348    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 8349}
 8350
 8351#[gpui::test]
 8352async fn test_select_all_matches(cx: &mut TestAppContext) {
 8353    init_test(cx, |_| {});
 8354
 8355    let mut cx = EditorTestContext::new(cx).await;
 8356
 8357    // Test caret-only selections
 8358    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8359    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8360        .unwrap();
 8361    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8362
 8363    // Test left-to-right selections
 8364    cx.set_state("abc\n«abcˇ»\nabc");
 8365    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8366        .unwrap();
 8367    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 8368
 8369    // Test right-to-left selections
 8370    cx.set_state("abc\n«ˇabc»\nabc");
 8371    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8372        .unwrap();
 8373    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 8374
 8375    // Test selecting whitespace with caret selection
 8376    cx.set_state("abc\nˇ   abc\nabc");
 8377    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8378        .unwrap();
 8379    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 8380
 8381    // Test selecting whitespace with left-to-right selection
 8382    cx.set_state("abc\n«ˇ  »abc\nabc");
 8383    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8384        .unwrap();
 8385    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 8386
 8387    // Test no matches with right-to-left selection
 8388    cx.set_state("abc\n«  ˇ»abc\nabc");
 8389    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8390        .unwrap();
 8391    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 8392
 8393    // Test with a single word and clip_at_line_ends=true (#29823)
 8394    cx.set_state("aˇbc");
 8395    cx.update_editor(|e, window, cx| {
 8396        e.set_clip_at_line_ends(true, cx);
 8397        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8398        e.set_clip_at_line_ends(false, cx);
 8399    });
 8400    cx.assert_editor_state("«abcˇ»");
 8401}
 8402
 8403#[gpui::test]
 8404async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 8405    init_test(cx, |_| {});
 8406
 8407    let mut cx = EditorTestContext::new(cx).await;
 8408
 8409    let large_body_1 = "\nd".repeat(200);
 8410    let large_body_2 = "\ne".repeat(200);
 8411
 8412    cx.set_state(&format!(
 8413        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 8414    ));
 8415    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 8416        let scroll_position = editor.scroll_position(cx);
 8417        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 8418        scroll_position
 8419    });
 8420
 8421    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8422        .unwrap();
 8423    cx.assert_editor_state(&format!(
 8424        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 8425    ));
 8426    let scroll_position_after_selection =
 8427        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 8428    assert_eq!(
 8429        initial_scroll_position, scroll_position_after_selection,
 8430        "Scroll position should not change after selecting all matches"
 8431    );
 8432}
 8433
 8434#[gpui::test]
 8435async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 8436    init_test(cx, |_| {});
 8437
 8438    let mut cx = EditorLspTestContext::new_rust(
 8439        lsp::ServerCapabilities {
 8440            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8441            ..Default::default()
 8442        },
 8443        cx,
 8444    )
 8445    .await;
 8446
 8447    cx.set_state(indoc! {"
 8448        line 1
 8449        line 2
 8450        linˇe 3
 8451        line 4
 8452        line 5
 8453    "});
 8454
 8455    // Make an edit
 8456    cx.update_editor(|editor, window, cx| {
 8457        editor.handle_input("X", window, cx);
 8458    });
 8459
 8460    // Move cursor to a different position
 8461    cx.update_editor(|editor, window, cx| {
 8462        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8463            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 8464        });
 8465    });
 8466
 8467    cx.assert_editor_state(indoc! {"
 8468        line 1
 8469        line 2
 8470        linXe 3
 8471        line 4
 8472        liˇne 5
 8473    "});
 8474
 8475    cx.lsp
 8476        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 8477            Ok(Some(vec![lsp::TextEdit::new(
 8478                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 8479                "PREFIX ".to_string(),
 8480            )]))
 8481        });
 8482
 8483    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 8484        .unwrap()
 8485        .await
 8486        .unwrap();
 8487
 8488    cx.assert_editor_state(indoc! {"
 8489        PREFIX line 1
 8490        line 2
 8491        linXe 3
 8492        line 4
 8493        liˇne 5
 8494    "});
 8495
 8496    // Undo formatting
 8497    cx.update_editor(|editor, window, cx| {
 8498        editor.undo(&Default::default(), window, cx);
 8499    });
 8500
 8501    // Verify cursor moved back to position after edit
 8502    cx.assert_editor_state(indoc! {"
 8503        line 1
 8504        line 2
 8505        linXˇe 3
 8506        line 4
 8507        line 5
 8508    "});
 8509}
 8510
 8511#[gpui::test]
 8512async fn test_undo_edit_prediction_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 8513    init_test(cx, |_| {});
 8514
 8515    let mut cx = EditorTestContext::new(cx).await;
 8516
 8517    let provider = cx.new(|_| FakeEditPredictionProvider::default());
 8518    cx.update_editor(|editor, window, cx| {
 8519        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 8520    });
 8521
 8522    cx.set_state(indoc! {"
 8523        line 1
 8524        line 2
 8525        linˇe 3
 8526        line 4
 8527        line 5
 8528        line 6
 8529        line 7
 8530        line 8
 8531        line 9
 8532        line 10
 8533    "});
 8534
 8535    let snapshot = cx.buffer_snapshot();
 8536    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 8537
 8538    cx.update(|_, cx| {
 8539        provider.update(cx, |provider, _| {
 8540            provider.set_edit_prediction(Some(edit_prediction::EditPrediction::Local {
 8541                id: None,
 8542                edits: vec![(edit_position..edit_position, "X".into())],
 8543                edit_preview: None,
 8544            }))
 8545        })
 8546    });
 8547
 8548    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 8549    cx.update_editor(|editor, window, cx| {
 8550        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 8551    });
 8552
 8553    cx.assert_editor_state(indoc! {"
 8554        line 1
 8555        line 2
 8556        lineXˇ 3
 8557        line 4
 8558        line 5
 8559        line 6
 8560        line 7
 8561        line 8
 8562        line 9
 8563        line 10
 8564    "});
 8565
 8566    cx.update_editor(|editor, window, cx| {
 8567        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8568            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 8569        });
 8570    });
 8571
 8572    cx.assert_editor_state(indoc! {"
 8573        line 1
 8574        line 2
 8575        lineX 3
 8576        line 4
 8577        line 5
 8578        line 6
 8579        line 7
 8580        line 8
 8581        line 9
 8582        liˇne 10
 8583    "});
 8584
 8585    cx.update_editor(|editor, window, cx| {
 8586        editor.undo(&Default::default(), window, cx);
 8587    });
 8588
 8589    cx.assert_editor_state(indoc! {"
 8590        line 1
 8591        line 2
 8592        lineˇ 3
 8593        line 4
 8594        line 5
 8595        line 6
 8596        line 7
 8597        line 8
 8598        line 9
 8599        line 10
 8600    "});
 8601}
 8602
 8603#[gpui::test]
 8604async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 8605    init_test(cx, |_| {});
 8606
 8607    let mut cx = EditorTestContext::new(cx).await;
 8608    cx.set_state(
 8609        r#"let foo = 2;
 8610lˇet foo = 2;
 8611let fooˇ = 2;
 8612let foo = 2;
 8613let foo = ˇ2;"#,
 8614    );
 8615
 8616    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8617        .unwrap();
 8618    cx.assert_editor_state(
 8619        r#"let foo = 2;
 8620«letˇ» foo = 2;
 8621let «fooˇ» = 2;
 8622let foo = 2;
 8623let foo = «2ˇ»;"#,
 8624    );
 8625
 8626    // noop for multiple selections with different contents
 8627    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8628        .unwrap();
 8629    cx.assert_editor_state(
 8630        r#"let foo = 2;
 8631«letˇ» foo = 2;
 8632let «fooˇ» = 2;
 8633let foo = 2;
 8634let foo = «2ˇ»;"#,
 8635    );
 8636
 8637    // Test last selection direction should be preserved
 8638    cx.set_state(
 8639        r#"let foo = 2;
 8640let foo = 2;
 8641let «fooˇ» = 2;
 8642let «ˇfoo» = 2;
 8643let foo = 2;"#,
 8644    );
 8645
 8646    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8647        .unwrap();
 8648    cx.assert_editor_state(
 8649        r#"let foo = 2;
 8650let foo = 2;
 8651let «fooˇ» = 2;
 8652let «ˇfoo» = 2;
 8653let «ˇfoo» = 2;"#,
 8654    );
 8655}
 8656
 8657#[gpui::test]
 8658async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 8659    init_test(cx, |_| {});
 8660
 8661    let mut cx =
 8662        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 8663
 8664    cx.assert_editor_state(indoc! {"
 8665        ˇbbb
 8666        ccc
 8667
 8668        bbb
 8669        ccc
 8670        "});
 8671    cx.dispatch_action(SelectPrevious::default());
 8672    cx.assert_editor_state(indoc! {"
 8673                «bbbˇ»
 8674                ccc
 8675
 8676                bbb
 8677                ccc
 8678                "});
 8679    cx.dispatch_action(SelectPrevious::default());
 8680    cx.assert_editor_state(indoc! {"
 8681                «bbbˇ»
 8682                ccc
 8683
 8684                «bbbˇ»
 8685                ccc
 8686                "});
 8687}
 8688
 8689#[gpui::test]
 8690async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 8691    init_test(cx, |_| {});
 8692
 8693    let mut cx = EditorTestContext::new(cx).await;
 8694    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8695
 8696    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8697        .unwrap();
 8698    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8699
 8700    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8701        .unwrap();
 8702    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8703
 8704    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8705    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8706
 8707    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8708    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8709
 8710    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8711        .unwrap();
 8712    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 8713
 8714    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8715        .unwrap();
 8716    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8717}
 8718
 8719#[gpui::test]
 8720async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 8721    init_test(cx, |_| {});
 8722
 8723    let mut cx = EditorTestContext::new(cx).await;
 8724    cx.set_state("");
 8725
 8726    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8727        .unwrap();
 8728    cx.assert_editor_state("«aˇ»");
 8729    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8730        .unwrap();
 8731    cx.assert_editor_state("«aˇ»");
 8732}
 8733
 8734#[gpui::test]
 8735async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 8736    init_test(cx, |_| {});
 8737
 8738    let mut cx = EditorTestContext::new(cx).await;
 8739    cx.set_state(
 8740        r#"let foo = 2;
 8741lˇet foo = 2;
 8742let fooˇ = 2;
 8743let foo = 2;
 8744let foo = ˇ2;"#,
 8745    );
 8746
 8747    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8748        .unwrap();
 8749    cx.assert_editor_state(
 8750        r#"let foo = 2;
 8751«letˇ» foo = 2;
 8752let «fooˇ» = 2;
 8753let foo = 2;
 8754let foo = «2ˇ»;"#,
 8755    );
 8756
 8757    // noop for multiple selections with different contents
 8758    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8759        .unwrap();
 8760    cx.assert_editor_state(
 8761        r#"let foo = 2;
 8762«letˇ» foo = 2;
 8763let «fooˇ» = 2;
 8764let foo = 2;
 8765let foo = «2ˇ»;"#,
 8766    );
 8767}
 8768
 8769#[gpui::test]
 8770async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 8771    init_test(cx, |_| {});
 8772
 8773    let mut cx = EditorTestContext::new(cx).await;
 8774    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8775
 8776    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8777        .unwrap();
 8778    // selection direction is preserved
 8779    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8780
 8781    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8782        .unwrap();
 8783    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8784
 8785    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8786    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8787
 8788    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8789    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8790
 8791    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8792        .unwrap();
 8793    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 8794
 8795    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8796        .unwrap();
 8797    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 8798}
 8799
 8800#[gpui::test]
 8801async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 8802    init_test(cx, |_| {});
 8803
 8804    let language = Arc::new(Language::new(
 8805        LanguageConfig::default(),
 8806        Some(tree_sitter_rust::LANGUAGE.into()),
 8807    ));
 8808
 8809    let text = r#"
 8810        use mod1::mod2::{mod3, mod4};
 8811
 8812        fn fn_1(param1: bool, param2: &str) {
 8813            let var1 = "text";
 8814        }
 8815    "#
 8816    .unindent();
 8817
 8818    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8819    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8820    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8821
 8822    editor
 8823        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8824        .await;
 8825
 8826    editor.update_in(cx, |editor, window, cx| {
 8827        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8828            s.select_display_ranges([
 8829                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 8830                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 8831                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 8832            ]);
 8833        });
 8834        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8835    });
 8836    editor.update(cx, |editor, cx| {
 8837        assert_text_with_selections(
 8838            editor,
 8839            indoc! {r#"
 8840                use mod1::mod2::{mod3, «mod4ˇ»};
 8841
 8842                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8843                    let var1 = "«ˇtext»";
 8844                }
 8845            "#},
 8846            cx,
 8847        );
 8848    });
 8849
 8850    editor.update_in(cx, |editor, window, cx| {
 8851        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8852    });
 8853    editor.update(cx, |editor, cx| {
 8854        assert_text_with_selections(
 8855            editor,
 8856            indoc! {r#"
 8857                use mod1::mod2::«{mod3, mod4}ˇ»;
 8858
 8859                «ˇfn fn_1(param1: bool, param2: &str) {
 8860                    let var1 = "text";
 8861 8862            "#},
 8863            cx,
 8864        );
 8865    });
 8866
 8867    editor.update_in(cx, |editor, window, cx| {
 8868        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8869    });
 8870    assert_eq!(
 8871        editor.update(cx, |editor, cx| editor
 8872            .selections
 8873            .display_ranges(&editor.display_snapshot(cx))),
 8874        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8875    );
 8876
 8877    // Trying to expand the selected syntax node one more time has no effect.
 8878    editor.update_in(cx, |editor, window, cx| {
 8879        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8880    });
 8881    assert_eq!(
 8882        editor.update(cx, |editor, cx| editor
 8883            .selections
 8884            .display_ranges(&editor.display_snapshot(cx))),
 8885        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8886    );
 8887
 8888    editor.update_in(cx, |editor, window, cx| {
 8889        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8890    });
 8891    editor.update(cx, |editor, cx| {
 8892        assert_text_with_selections(
 8893            editor,
 8894            indoc! {r#"
 8895                use mod1::mod2::«{mod3, mod4}ˇ»;
 8896
 8897                «ˇfn fn_1(param1: bool, param2: &str) {
 8898                    let var1 = "text";
 8899 8900            "#},
 8901            cx,
 8902        );
 8903    });
 8904
 8905    editor.update_in(cx, |editor, window, cx| {
 8906        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8907    });
 8908    editor.update(cx, |editor, cx| {
 8909        assert_text_with_selections(
 8910            editor,
 8911            indoc! {r#"
 8912                use mod1::mod2::{mod3, «mod4ˇ»};
 8913
 8914                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8915                    let var1 = "«ˇtext»";
 8916                }
 8917            "#},
 8918            cx,
 8919        );
 8920    });
 8921
 8922    editor.update_in(cx, |editor, window, cx| {
 8923        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8924    });
 8925    editor.update(cx, |editor, cx| {
 8926        assert_text_with_selections(
 8927            editor,
 8928            indoc! {r#"
 8929                use mod1::mod2::{mod3, moˇd4};
 8930
 8931                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 8932                    let var1 = "teˇxt";
 8933                }
 8934            "#},
 8935            cx,
 8936        );
 8937    });
 8938
 8939    // Trying to shrink the selected syntax node one more time has no effect.
 8940    editor.update_in(cx, |editor, window, cx| {
 8941        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8942    });
 8943    editor.update_in(cx, |editor, _, cx| {
 8944        assert_text_with_selections(
 8945            editor,
 8946            indoc! {r#"
 8947                use mod1::mod2::{mod3, moˇd4};
 8948
 8949                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 8950                    let var1 = "teˇxt";
 8951                }
 8952            "#},
 8953            cx,
 8954        );
 8955    });
 8956
 8957    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 8958    // a fold.
 8959    editor.update_in(cx, |editor, window, cx| {
 8960        editor.fold_creases(
 8961            vec![
 8962                Crease::simple(
 8963                    Point::new(0, 21)..Point::new(0, 24),
 8964                    FoldPlaceholder::test(),
 8965                ),
 8966                Crease::simple(
 8967                    Point::new(3, 20)..Point::new(3, 22),
 8968                    FoldPlaceholder::test(),
 8969                ),
 8970            ],
 8971            true,
 8972            window,
 8973            cx,
 8974        );
 8975        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8976    });
 8977    editor.update(cx, |editor, cx| {
 8978        assert_text_with_selections(
 8979            editor,
 8980            indoc! {r#"
 8981                use mod1::mod2::«{mod3, mod4}ˇ»;
 8982
 8983                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8984                    let var1 = "«ˇtext»";
 8985                }
 8986            "#},
 8987            cx,
 8988        );
 8989    });
 8990}
 8991
 8992#[gpui::test]
 8993async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 8994    init_test(cx, |_| {});
 8995
 8996    let language = Arc::new(Language::new(
 8997        LanguageConfig::default(),
 8998        Some(tree_sitter_rust::LANGUAGE.into()),
 8999    ));
 9000
 9001    let text = "let a = 2;";
 9002
 9003    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9004    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9005    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9006
 9007    editor
 9008        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9009        .await;
 9010
 9011    // Test case 1: Cursor at end of word
 9012    editor.update_in(cx, |editor, window, cx| {
 9013        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9014            s.select_display_ranges([
 9015                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 9016            ]);
 9017        });
 9018    });
 9019    editor.update(cx, |editor, cx| {
 9020        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 9021    });
 9022    editor.update_in(cx, |editor, window, cx| {
 9023        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9024    });
 9025    editor.update(cx, |editor, cx| {
 9026        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 9027    });
 9028    editor.update_in(cx, |editor, window, cx| {
 9029        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9030    });
 9031    editor.update(cx, |editor, cx| {
 9032        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 9033    });
 9034
 9035    // Test case 2: Cursor at end of statement
 9036    editor.update_in(cx, |editor, window, cx| {
 9037        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9038            s.select_display_ranges([
 9039                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 9040            ]);
 9041        });
 9042    });
 9043    editor.update(cx, |editor, cx| {
 9044        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 9045    });
 9046    editor.update_in(cx, |editor, window, cx| {
 9047        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9048    });
 9049    editor.update(cx, |editor, cx| {
 9050        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 9051    });
 9052}
 9053
 9054#[gpui::test]
 9055async fn test_select_larger_syntax_node_for_cursor_at_symbol(cx: &mut TestAppContext) {
 9056    init_test(cx, |_| {});
 9057
 9058    let language = Arc::new(Language::new(
 9059        LanguageConfig {
 9060            name: "JavaScript".into(),
 9061            ..Default::default()
 9062        },
 9063        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 9064    ));
 9065
 9066    let text = r#"
 9067        let a = {
 9068            key: "value",
 9069        };
 9070    "#
 9071    .unindent();
 9072
 9073    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9074    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9075    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9076
 9077    editor
 9078        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9079        .await;
 9080
 9081    // Test case 1: Cursor after '{'
 9082    editor.update_in(cx, |editor, window, cx| {
 9083        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9084            s.select_display_ranges([
 9085                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 9)
 9086            ]);
 9087        });
 9088    });
 9089    editor.update(cx, |editor, cx| {
 9090        assert_text_with_selections(
 9091            editor,
 9092            indoc! {r#"
 9093                let a = {ˇ
 9094                    key: "value",
 9095                };
 9096            "#},
 9097            cx,
 9098        );
 9099    });
 9100    editor.update_in(cx, |editor, window, cx| {
 9101        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9102    });
 9103    editor.update(cx, |editor, cx| {
 9104        assert_text_with_selections(
 9105            editor,
 9106            indoc! {r#"
 9107                let a = «ˇ{
 9108                    key: "value",
 9109                }»;
 9110            "#},
 9111            cx,
 9112        );
 9113    });
 9114
 9115    // Test case 2: Cursor after ':'
 9116    editor.update_in(cx, |editor, window, cx| {
 9117        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9118            s.select_display_ranges([
 9119                DisplayPoint::new(DisplayRow(1), 8)..DisplayPoint::new(DisplayRow(1), 8)
 9120            ]);
 9121        });
 9122    });
 9123    editor.update(cx, |editor, cx| {
 9124        assert_text_with_selections(
 9125            editor,
 9126            indoc! {r#"
 9127                let a = {
 9128                    key:ˇ "value",
 9129                };
 9130            "#},
 9131            cx,
 9132        );
 9133    });
 9134    editor.update_in(cx, |editor, window, cx| {
 9135        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9136    });
 9137    editor.update(cx, |editor, cx| {
 9138        assert_text_with_selections(
 9139            editor,
 9140            indoc! {r#"
 9141                let a = {
 9142                    «ˇkey: "value"»,
 9143                };
 9144            "#},
 9145            cx,
 9146        );
 9147    });
 9148    editor.update_in(cx, |editor, window, cx| {
 9149        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9150    });
 9151    editor.update(cx, |editor, cx| {
 9152        assert_text_with_selections(
 9153            editor,
 9154            indoc! {r#"
 9155                let a = «ˇ{
 9156                    key: "value",
 9157                }»;
 9158            "#},
 9159            cx,
 9160        );
 9161    });
 9162
 9163    // Test case 3: Cursor after ','
 9164    editor.update_in(cx, |editor, window, cx| {
 9165        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9166            s.select_display_ranges([
 9167                DisplayPoint::new(DisplayRow(1), 17)..DisplayPoint::new(DisplayRow(1), 17)
 9168            ]);
 9169        });
 9170    });
 9171    editor.update(cx, |editor, cx| {
 9172        assert_text_with_selections(
 9173            editor,
 9174            indoc! {r#"
 9175                let a = {
 9176                    key: "value",ˇ
 9177                };
 9178            "#},
 9179            cx,
 9180        );
 9181    });
 9182    editor.update_in(cx, |editor, window, cx| {
 9183        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9184    });
 9185    editor.update(cx, |editor, cx| {
 9186        assert_text_with_selections(
 9187            editor,
 9188            indoc! {r#"
 9189                let a = «ˇ{
 9190                    key: "value",
 9191                }»;
 9192            "#},
 9193            cx,
 9194        );
 9195    });
 9196
 9197    // Test case 4: Cursor after ';'
 9198    editor.update_in(cx, |editor, window, cx| {
 9199        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9200            s.select_display_ranges([
 9201                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)
 9202            ]);
 9203        });
 9204    });
 9205    editor.update(cx, |editor, cx| {
 9206        assert_text_with_selections(
 9207            editor,
 9208            indoc! {r#"
 9209                let a = {
 9210                    key: "value",
 9211                };ˇ
 9212            "#},
 9213            cx,
 9214        );
 9215    });
 9216    editor.update_in(cx, |editor, window, cx| {
 9217        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9218    });
 9219    editor.update(cx, |editor, cx| {
 9220        assert_text_with_selections(
 9221            editor,
 9222            indoc! {r#"
 9223                «ˇlet a = {
 9224                    key: "value",
 9225                };
 9226                »"#},
 9227            cx,
 9228        );
 9229    });
 9230}
 9231
 9232#[gpui::test]
 9233async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 9234    init_test(cx, |_| {});
 9235
 9236    let language = Arc::new(Language::new(
 9237        LanguageConfig::default(),
 9238        Some(tree_sitter_rust::LANGUAGE.into()),
 9239    ));
 9240
 9241    let text = r#"
 9242        use mod1::mod2::{mod3, mod4};
 9243
 9244        fn fn_1(param1: bool, param2: &str) {
 9245            let var1 = "hello world";
 9246        }
 9247    "#
 9248    .unindent();
 9249
 9250    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9251    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9252    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9253
 9254    editor
 9255        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9256        .await;
 9257
 9258    // Test 1: Cursor on a letter of a string word
 9259    editor.update_in(cx, |editor, window, cx| {
 9260        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9261            s.select_display_ranges([
 9262                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 9263            ]);
 9264        });
 9265    });
 9266    editor.update_in(cx, |editor, window, cx| {
 9267        assert_text_with_selections(
 9268            editor,
 9269            indoc! {r#"
 9270                use mod1::mod2::{mod3, mod4};
 9271
 9272                fn fn_1(param1: bool, param2: &str) {
 9273                    let var1 = "hˇello world";
 9274                }
 9275            "#},
 9276            cx,
 9277        );
 9278        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9279        assert_text_with_selections(
 9280            editor,
 9281            indoc! {r#"
 9282                use mod1::mod2::{mod3, mod4};
 9283
 9284                fn fn_1(param1: bool, param2: &str) {
 9285                    let var1 = "«ˇhello» world";
 9286                }
 9287            "#},
 9288            cx,
 9289        );
 9290    });
 9291
 9292    // Test 2: Partial selection within a word
 9293    editor.update_in(cx, |editor, window, cx| {
 9294        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9295            s.select_display_ranges([
 9296                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 9297            ]);
 9298        });
 9299    });
 9300    editor.update_in(cx, |editor, window, cx| {
 9301        assert_text_with_selections(
 9302            editor,
 9303            indoc! {r#"
 9304                use mod1::mod2::{mod3, mod4};
 9305
 9306                fn fn_1(param1: bool, param2: &str) {
 9307                    let var1 = "h«elˇ»lo world";
 9308                }
 9309            "#},
 9310            cx,
 9311        );
 9312        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9313        assert_text_with_selections(
 9314            editor,
 9315            indoc! {r#"
 9316                use mod1::mod2::{mod3, mod4};
 9317
 9318                fn fn_1(param1: bool, param2: &str) {
 9319                    let var1 = "«ˇhello» world";
 9320                }
 9321            "#},
 9322            cx,
 9323        );
 9324    });
 9325
 9326    // Test 3: Complete word already selected
 9327    editor.update_in(cx, |editor, window, cx| {
 9328        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9329            s.select_display_ranges([
 9330                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 9331            ]);
 9332        });
 9333    });
 9334    editor.update_in(cx, |editor, window, cx| {
 9335        assert_text_with_selections(
 9336            editor,
 9337            indoc! {r#"
 9338                use mod1::mod2::{mod3, mod4};
 9339
 9340                fn fn_1(param1: bool, param2: &str) {
 9341                    let var1 = "«helloˇ» world";
 9342                }
 9343            "#},
 9344            cx,
 9345        );
 9346        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9347        assert_text_with_selections(
 9348            editor,
 9349            indoc! {r#"
 9350                use mod1::mod2::{mod3, mod4};
 9351
 9352                fn fn_1(param1: bool, param2: &str) {
 9353                    let var1 = "«hello worldˇ»";
 9354                }
 9355            "#},
 9356            cx,
 9357        );
 9358    });
 9359
 9360    // Test 4: Selection spanning across words
 9361    editor.update_in(cx, |editor, window, cx| {
 9362        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9363            s.select_display_ranges([
 9364                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 9365            ]);
 9366        });
 9367    });
 9368    editor.update_in(cx, |editor, window, cx| {
 9369        assert_text_with_selections(
 9370            editor,
 9371            indoc! {r#"
 9372                use mod1::mod2::{mod3, mod4};
 9373
 9374                fn fn_1(param1: bool, param2: &str) {
 9375                    let var1 = "hel«lo woˇ»rld";
 9376                }
 9377            "#},
 9378            cx,
 9379        );
 9380        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9381        assert_text_with_selections(
 9382            editor,
 9383            indoc! {r#"
 9384                use mod1::mod2::{mod3, mod4};
 9385
 9386                fn fn_1(param1: bool, param2: &str) {
 9387                    let var1 = "«ˇhello world»";
 9388                }
 9389            "#},
 9390            cx,
 9391        );
 9392    });
 9393
 9394    // Test 5: Expansion beyond string
 9395    editor.update_in(cx, |editor, window, cx| {
 9396        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9397        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9398        assert_text_with_selections(
 9399            editor,
 9400            indoc! {r#"
 9401                use mod1::mod2::{mod3, mod4};
 9402
 9403                fn fn_1(param1: bool, param2: &str) {
 9404                    «ˇlet var1 = "hello world";»
 9405                }
 9406            "#},
 9407            cx,
 9408        );
 9409    });
 9410}
 9411
 9412#[gpui::test]
 9413async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) {
 9414    init_test(cx, |_| {});
 9415
 9416    let mut cx = EditorTestContext::new(cx).await;
 9417
 9418    let language = Arc::new(Language::new(
 9419        LanguageConfig::default(),
 9420        Some(tree_sitter_rust::LANGUAGE.into()),
 9421    ));
 9422
 9423    cx.update_buffer(|buffer, cx| {
 9424        buffer.set_language(Some(language), cx);
 9425    });
 9426
 9427    cx.set_state(indoc! { r#"use mod1::{mod2::{«mod3ˇ», mod4}, mod5::{mod6, «mod7ˇ»}};"# });
 9428    cx.update_editor(|editor, window, cx| {
 9429        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9430    });
 9431
 9432    cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# });
 9433
 9434    cx.set_state(indoc! { r#"fn a() {
 9435          // what
 9436          // a
 9437          // ˇlong
 9438          // method
 9439          // I
 9440          // sure
 9441          // hope
 9442          // it
 9443          // works
 9444    }"# });
 9445
 9446    let buffer = cx.update_multibuffer(|multibuffer, _| multibuffer.as_singleton().unwrap());
 9447    let multi_buffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 9448    cx.update(|_, cx| {
 9449        multi_buffer.update(cx, |multi_buffer, cx| {
 9450            multi_buffer.set_excerpts_for_path(
 9451                PathKey::for_buffer(&buffer, cx),
 9452                buffer,
 9453                [Point::new(1, 0)..Point::new(1, 0)],
 9454                3,
 9455                cx,
 9456            );
 9457        });
 9458    });
 9459
 9460    let editor2 = cx.new_window_entity(|window, cx| {
 9461        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
 9462    });
 9463
 9464    let mut cx = EditorTestContext::for_editor_in(editor2, &mut cx).await;
 9465    cx.update_editor(|editor, window, cx| {
 9466        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 9467            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)]);
 9468        })
 9469    });
 9470
 9471    cx.assert_editor_state(indoc! { "
 9472        fn a() {
 9473              // what
 9474              // a
 9475        ˇ      // long
 9476              // method"});
 9477
 9478    cx.update_editor(|editor, window, cx| {
 9479        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9480    });
 9481
 9482    // Although we could potentially make the action work when the syntax node
 9483    // is half-hidden, it seems a bit dangerous as you can't easily tell what it
 9484    // did. Maybe we could also expand the excerpt to contain the range?
 9485    cx.assert_editor_state(indoc! { "
 9486        fn a() {
 9487              // what
 9488              // a
 9489        ˇ      // long
 9490              // method"});
 9491}
 9492
 9493#[gpui::test]
 9494async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 9495    init_test(cx, |_| {});
 9496
 9497    let base_text = r#"
 9498        impl A {
 9499            // this is an uncommitted comment
 9500
 9501            fn b() {
 9502                c();
 9503            }
 9504
 9505            // this is another uncommitted comment
 9506
 9507            fn d() {
 9508                // e
 9509                // f
 9510            }
 9511        }
 9512
 9513        fn g() {
 9514            // h
 9515        }
 9516    "#
 9517    .unindent();
 9518
 9519    let text = r#"
 9520        ˇimpl A {
 9521
 9522            fn b() {
 9523                c();
 9524            }
 9525
 9526            fn d() {
 9527                // e
 9528                // f
 9529            }
 9530        }
 9531
 9532        fn g() {
 9533            // h
 9534        }
 9535    "#
 9536    .unindent();
 9537
 9538    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9539    cx.set_state(&text);
 9540    cx.set_head_text(&base_text);
 9541    cx.update_editor(|editor, window, cx| {
 9542        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 9543    });
 9544
 9545    cx.assert_state_with_diff(
 9546        "
 9547        ˇimpl A {
 9548      -     // this is an uncommitted comment
 9549
 9550            fn b() {
 9551                c();
 9552            }
 9553
 9554      -     // this is another uncommitted comment
 9555      -
 9556            fn d() {
 9557                // e
 9558                // f
 9559            }
 9560        }
 9561
 9562        fn g() {
 9563            // h
 9564        }
 9565    "
 9566        .unindent(),
 9567    );
 9568
 9569    let expected_display_text = "
 9570        impl A {
 9571            // this is an uncommitted comment
 9572
 9573            fn b() {
 9574 9575            }
 9576
 9577            // this is another uncommitted comment
 9578
 9579            fn d() {
 9580 9581            }
 9582        }
 9583
 9584        fn g() {
 9585 9586        }
 9587        "
 9588    .unindent();
 9589
 9590    cx.update_editor(|editor, window, cx| {
 9591        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 9592        assert_eq!(editor.display_text(cx), expected_display_text);
 9593    });
 9594}
 9595
 9596#[gpui::test]
 9597async fn test_autoindent(cx: &mut TestAppContext) {
 9598    init_test(cx, |_| {});
 9599
 9600    let language = Arc::new(
 9601        Language::new(
 9602            LanguageConfig {
 9603                brackets: BracketPairConfig {
 9604                    pairs: vec![
 9605                        BracketPair {
 9606                            start: "{".to_string(),
 9607                            end: "}".to_string(),
 9608                            close: false,
 9609                            surround: false,
 9610                            newline: true,
 9611                        },
 9612                        BracketPair {
 9613                            start: "(".to_string(),
 9614                            end: ")".to_string(),
 9615                            close: false,
 9616                            surround: false,
 9617                            newline: true,
 9618                        },
 9619                    ],
 9620                    ..Default::default()
 9621                },
 9622                ..Default::default()
 9623            },
 9624            Some(tree_sitter_rust::LANGUAGE.into()),
 9625        )
 9626        .with_indents_query(
 9627            r#"
 9628                (_ "(" ")" @end) @indent
 9629                (_ "{" "}" @end) @indent
 9630            "#,
 9631        )
 9632        .unwrap(),
 9633    );
 9634
 9635    let text = "fn a() {}";
 9636
 9637    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9638    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9639    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9640    editor
 9641        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9642        .await;
 9643
 9644    editor.update_in(cx, |editor, window, cx| {
 9645        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9646            s.select_ranges([5..5, 8..8, 9..9])
 9647        });
 9648        editor.newline(&Newline, window, cx);
 9649        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 9650        assert_eq!(
 9651            editor.selections.ranges(&editor.display_snapshot(cx)),
 9652            &[
 9653                Point::new(1, 4)..Point::new(1, 4),
 9654                Point::new(3, 4)..Point::new(3, 4),
 9655                Point::new(5, 0)..Point::new(5, 0)
 9656            ]
 9657        );
 9658    });
 9659}
 9660
 9661#[gpui::test]
 9662async fn test_autoindent_disabled(cx: &mut TestAppContext) {
 9663    init_test(cx, |settings| settings.defaults.auto_indent = Some(false));
 9664
 9665    let language = Arc::new(
 9666        Language::new(
 9667            LanguageConfig {
 9668                brackets: BracketPairConfig {
 9669                    pairs: vec![
 9670                        BracketPair {
 9671                            start: "{".to_string(),
 9672                            end: "}".to_string(),
 9673                            close: false,
 9674                            surround: false,
 9675                            newline: true,
 9676                        },
 9677                        BracketPair {
 9678                            start: "(".to_string(),
 9679                            end: ")".to_string(),
 9680                            close: false,
 9681                            surround: false,
 9682                            newline: true,
 9683                        },
 9684                    ],
 9685                    ..Default::default()
 9686                },
 9687                ..Default::default()
 9688            },
 9689            Some(tree_sitter_rust::LANGUAGE.into()),
 9690        )
 9691        .with_indents_query(
 9692            r#"
 9693                (_ "(" ")" @end) @indent
 9694                (_ "{" "}" @end) @indent
 9695            "#,
 9696        )
 9697        .unwrap(),
 9698    );
 9699
 9700    let text = "fn a() {}";
 9701
 9702    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9703    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9704    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9705    editor
 9706        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9707        .await;
 9708
 9709    editor.update_in(cx, |editor, window, cx| {
 9710        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9711            s.select_ranges([5..5, 8..8, 9..9])
 9712        });
 9713        editor.newline(&Newline, window, cx);
 9714        assert_eq!(
 9715            editor.text(cx),
 9716            indoc!(
 9717                "
 9718                fn a(
 9719
 9720                ) {
 9721
 9722                }
 9723                "
 9724            )
 9725        );
 9726        assert_eq!(
 9727            editor.selections.ranges(&editor.display_snapshot(cx)),
 9728            &[
 9729                Point::new(1, 0)..Point::new(1, 0),
 9730                Point::new(3, 0)..Point::new(3, 0),
 9731                Point::new(5, 0)..Point::new(5, 0)
 9732            ]
 9733        );
 9734    });
 9735}
 9736
 9737#[gpui::test]
 9738async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
 9739    init_test(cx, |settings| {
 9740        settings.defaults.auto_indent = Some(true);
 9741        settings.languages.0.insert(
 9742            "python".into(),
 9743            LanguageSettingsContent {
 9744                auto_indent: Some(false),
 9745                ..Default::default()
 9746            },
 9747        );
 9748    });
 9749
 9750    let mut cx = EditorTestContext::new(cx).await;
 9751
 9752    let injected_language = Arc::new(
 9753        Language::new(
 9754            LanguageConfig {
 9755                brackets: BracketPairConfig {
 9756                    pairs: vec![
 9757                        BracketPair {
 9758                            start: "{".to_string(),
 9759                            end: "}".to_string(),
 9760                            close: false,
 9761                            surround: false,
 9762                            newline: true,
 9763                        },
 9764                        BracketPair {
 9765                            start: "(".to_string(),
 9766                            end: ")".to_string(),
 9767                            close: true,
 9768                            surround: false,
 9769                            newline: true,
 9770                        },
 9771                    ],
 9772                    ..Default::default()
 9773                },
 9774                name: "python".into(),
 9775                ..Default::default()
 9776            },
 9777            Some(tree_sitter_python::LANGUAGE.into()),
 9778        )
 9779        .with_indents_query(
 9780            r#"
 9781                (_ "(" ")" @end) @indent
 9782                (_ "{" "}" @end) @indent
 9783            "#,
 9784        )
 9785        .unwrap(),
 9786    );
 9787
 9788    let language = Arc::new(
 9789        Language::new(
 9790            LanguageConfig {
 9791                brackets: BracketPairConfig {
 9792                    pairs: vec![
 9793                        BracketPair {
 9794                            start: "{".to_string(),
 9795                            end: "}".to_string(),
 9796                            close: false,
 9797                            surround: false,
 9798                            newline: true,
 9799                        },
 9800                        BracketPair {
 9801                            start: "(".to_string(),
 9802                            end: ")".to_string(),
 9803                            close: true,
 9804                            surround: false,
 9805                            newline: true,
 9806                        },
 9807                    ],
 9808                    ..Default::default()
 9809                },
 9810                name: LanguageName::new("rust"),
 9811                ..Default::default()
 9812            },
 9813            Some(tree_sitter_rust::LANGUAGE.into()),
 9814        )
 9815        .with_indents_query(
 9816            r#"
 9817                (_ "(" ")" @end) @indent
 9818                (_ "{" "}" @end) @indent
 9819            "#,
 9820        )
 9821        .unwrap()
 9822        .with_injection_query(
 9823            r#"
 9824            (macro_invocation
 9825                macro: (identifier) @_macro_name
 9826                (token_tree) @injection.content
 9827                (#set! injection.language "python"))
 9828           "#,
 9829        )
 9830        .unwrap(),
 9831    );
 9832
 9833    cx.language_registry().add(injected_language);
 9834    cx.language_registry().add(language.clone());
 9835
 9836    cx.update_buffer(|buffer, cx| {
 9837        buffer.set_language(Some(language), cx);
 9838    });
 9839
 9840    cx.set_state(r#"struct A {ˇ}"#);
 9841
 9842    cx.update_editor(|editor, window, cx| {
 9843        editor.newline(&Default::default(), window, cx);
 9844    });
 9845
 9846    cx.assert_editor_state(indoc!(
 9847        "struct A {
 9848            ˇ
 9849        }"
 9850    ));
 9851
 9852    cx.set_state(r#"select_biased!(ˇ)"#);
 9853
 9854    cx.update_editor(|editor, window, cx| {
 9855        editor.newline(&Default::default(), window, cx);
 9856        editor.handle_input("def ", window, cx);
 9857        editor.handle_input("(", window, cx);
 9858        editor.newline(&Default::default(), window, cx);
 9859        editor.handle_input("a", window, cx);
 9860    });
 9861
 9862    cx.assert_editor_state(indoc!(
 9863        "select_biased!(
 9864        def (
 9865 9866        )
 9867        )"
 9868    ));
 9869}
 9870
 9871#[gpui::test]
 9872async fn test_autoindent_selections(cx: &mut TestAppContext) {
 9873    init_test(cx, |_| {});
 9874
 9875    {
 9876        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9877        cx.set_state(indoc! {"
 9878            impl A {
 9879
 9880                fn b() {}
 9881
 9882            «fn c() {
 9883
 9884            }ˇ»
 9885            }
 9886        "});
 9887
 9888        cx.update_editor(|editor, window, cx| {
 9889            editor.autoindent(&Default::default(), window, cx);
 9890        });
 9891
 9892        cx.assert_editor_state(indoc! {"
 9893            impl A {
 9894
 9895                fn b() {}
 9896
 9897                «fn c() {
 9898
 9899                }ˇ»
 9900            }
 9901        "});
 9902    }
 9903
 9904    {
 9905        let mut cx = EditorTestContext::new_multibuffer(
 9906            cx,
 9907            [indoc! { "
 9908                impl A {
 9909                «
 9910                // a
 9911                fn b(){}
 9912                »
 9913                «
 9914                    }
 9915                    fn c(){}
 9916                »
 9917            "}],
 9918        );
 9919
 9920        let buffer = cx.update_editor(|editor, _, cx| {
 9921            let buffer = editor.buffer().update(cx, |buffer, _| {
 9922                buffer.all_buffers().iter().next().unwrap().clone()
 9923            });
 9924            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 9925            buffer
 9926        });
 9927
 9928        cx.run_until_parked();
 9929        cx.update_editor(|editor, window, cx| {
 9930            editor.select_all(&Default::default(), window, cx);
 9931            editor.autoindent(&Default::default(), window, cx)
 9932        });
 9933        cx.run_until_parked();
 9934
 9935        cx.update(|_, cx| {
 9936            assert_eq!(
 9937                buffer.read(cx).text(),
 9938                indoc! { "
 9939                    impl A {
 9940
 9941                        // a
 9942                        fn b(){}
 9943
 9944
 9945                    }
 9946                    fn c(){}
 9947
 9948                " }
 9949            )
 9950        });
 9951    }
 9952}
 9953
 9954#[gpui::test]
 9955async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 9956    init_test(cx, |_| {});
 9957
 9958    let mut cx = EditorTestContext::new(cx).await;
 9959
 9960    let language = Arc::new(Language::new(
 9961        LanguageConfig {
 9962            brackets: BracketPairConfig {
 9963                pairs: vec![
 9964                    BracketPair {
 9965                        start: "{".to_string(),
 9966                        end: "}".to_string(),
 9967                        close: true,
 9968                        surround: true,
 9969                        newline: true,
 9970                    },
 9971                    BracketPair {
 9972                        start: "(".to_string(),
 9973                        end: ")".to_string(),
 9974                        close: true,
 9975                        surround: true,
 9976                        newline: true,
 9977                    },
 9978                    BracketPair {
 9979                        start: "/*".to_string(),
 9980                        end: " */".to_string(),
 9981                        close: true,
 9982                        surround: true,
 9983                        newline: true,
 9984                    },
 9985                    BracketPair {
 9986                        start: "[".to_string(),
 9987                        end: "]".to_string(),
 9988                        close: false,
 9989                        surround: false,
 9990                        newline: true,
 9991                    },
 9992                    BracketPair {
 9993                        start: "\"".to_string(),
 9994                        end: "\"".to_string(),
 9995                        close: true,
 9996                        surround: true,
 9997                        newline: false,
 9998                    },
 9999                    BracketPair {
10000                        start: "<".to_string(),
10001                        end: ">".to_string(),
10002                        close: false,
10003                        surround: true,
10004                        newline: true,
10005                    },
10006                ],
10007                ..Default::default()
10008            },
10009            autoclose_before: "})]".to_string(),
10010            ..Default::default()
10011        },
10012        Some(tree_sitter_rust::LANGUAGE.into()),
10013    ));
10014
10015    cx.language_registry().add(language.clone());
10016    cx.update_buffer(|buffer, cx| {
10017        buffer.set_language(Some(language), cx);
10018    });
10019
10020    cx.set_state(
10021        &r#"
10022            🏀ˇ
10023            εˇ
10024            ❤️ˇ
10025        "#
10026        .unindent(),
10027    );
10028
10029    // autoclose multiple nested brackets at multiple cursors
10030    cx.update_editor(|editor, window, cx| {
10031        editor.handle_input("{", window, cx);
10032        editor.handle_input("{", window, cx);
10033        editor.handle_input("{", window, cx);
10034    });
10035    cx.assert_editor_state(
10036        &"
10037            🏀{{{ˇ}}}
10038            ε{{{ˇ}}}
10039            ❤️{{{ˇ}}}
10040        "
10041        .unindent(),
10042    );
10043
10044    // insert a different closing bracket
10045    cx.update_editor(|editor, window, cx| {
10046        editor.handle_input(")", window, cx);
10047    });
10048    cx.assert_editor_state(
10049        &"
10050            🏀{{{)ˇ}}}
10051            ε{{{)ˇ}}}
10052            ❤️{{{)ˇ}}}
10053        "
10054        .unindent(),
10055    );
10056
10057    // skip over the auto-closed brackets when typing a closing bracket
10058    cx.update_editor(|editor, window, cx| {
10059        editor.move_right(&MoveRight, window, cx);
10060        editor.handle_input("}", window, cx);
10061        editor.handle_input("}", window, cx);
10062        editor.handle_input("}", window, cx);
10063    });
10064    cx.assert_editor_state(
10065        &"
10066            🏀{{{)}}}}ˇ
10067            ε{{{)}}}}ˇ
10068            ❤️{{{)}}}}ˇ
10069        "
10070        .unindent(),
10071    );
10072
10073    // autoclose multi-character pairs
10074    cx.set_state(
10075        &"
10076            ˇ
10077            ˇ
10078        "
10079        .unindent(),
10080    );
10081    cx.update_editor(|editor, window, cx| {
10082        editor.handle_input("/", window, cx);
10083        editor.handle_input("*", window, cx);
10084    });
10085    cx.assert_editor_state(
10086        &"
10087            /*ˇ */
10088            /*ˇ */
10089        "
10090        .unindent(),
10091    );
10092
10093    // one cursor autocloses a multi-character pair, one cursor
10094    // does not autoclose.
10095    cx.set_state(
10096        &"
1009710098            ˇ
10099        "
10100        .unindent(),
10101    );
10102    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
10103    cx.assert_editor_state(
10104        &"
10105            /*ˇ */
1010610107        "
10108        .unindent(),
10109    );
10110
10111    // Don't autoclose if the next character isn't whitespace and isn't
10112    // listed in the language's "autoclose_before" section.
10113    cx.set_state("ˇa b");
10114    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10115    cx.assert_editor_state("{ˇa b");
10116
10117    // Don't autoclose if `close` is false for the bracket pair
10118    cx.set_state("ˇ");
10119    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
10120    cx.assert_editor_state("");
10121
10122    // Surround with brackets if text is selected
10123    cx.set_state("«aˇ» b");
10124    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10125    cx.assert_editor_state("{«aˇ»} b");
10126
10127    // Autoclose when not immediately after a word character
10128    cx.set_state("a ˇ");
10129    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10130    cx.assert_editor_state("a \"ˇ\"");
10131
10132    // Autoclose pair where the start and end characters are the same
10133    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10134    cx.assert_editor_state("a \"\"ˇ");
10135
10136    // Don't autoclose when immediately after a word character
10137    cx.set_state("");
10138    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10139    cx.assert_editor_state("a\"ˇ");
10140
10141    // Do autoclose when after a non-word character
10142    cx.set_state("");
10143    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10144    cx.assert_editor_state("{\"ˇ\"");
10145
10146    // Non identical pairs autoclose regardless of preceding character
10147    cx.set_state("");
10148    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10149    cx.assert_editor_state("a{ˇ}");
10150
10151    // Don't autoclose pair if autoclose is disabled
10152    cx.set_state("ˇ");
10153    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
10154    cx.assert_editor_state("");
10155
10156    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
10157    cx.set_state("«aˇ» b");
10158    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
10159    cx.assert_editor_state("<«aˇ»> b");
10160}
10161
10162#[gpui::test]
10163async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
10164    init_test(cx, |settings| {
10165        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10166    });
10167
10168    let mut cx = EditorTestContext::new(cx).await;
10169
10170    let language = Arc::new(Language::new(
10171        LanguageConfig {
10172            brackets: BracketPairConfig {
10173                pairs: vec![
10174                    BracketPair {
10175                        start: "{".to_string(),
10176                        end: "}".to_string(),
10177                        close: true,
10178                        surround: true,
10179                        newline: true,
10180                    },
10181                    BracketPair {
10182                        start: "(".to_string(),
10183                        end: ")".to_string(),
10184                        close: true,
10185                        surround: true,
10186                        newline: true,
10187                    },
10188                    BracketPair {
10189                        start: "[".to_string(),
10190                        end: "]".to_string(),
10191                        close: false,
10192                        surround: false,
10193                        newline: true,
10194                    },
10195                ],
10196                ..Default::default()
10197            },
10198            autoclose_before: "})]".to_string(),
10199            ..Default::default()
10200        },
10201        Some(tree_sitter_rust::LANGUAGE.into()),
10202    ));
10203
10204    cx.language_registry().add(language.clone());
10205    cx.update_buffer(|buffer, cx| {
10206        buffer.set_language(Some(language), cx);
10207    });
10208
10209    cx.set_state(
10210        &"
10211            ˇ
10212            ˇ
10213            ˇ
10214        "
10215        .unindent(),
10216    );
10217
10218    // ensure only matching closing brackets are skipped over
10219    cx.update_editor(|editor, window, cx| {
10220        editor.handle_input("}", window, cx);
10221        editor.move_left(&MoveLeft, window, cx);
10222        editor.handle_input(")", window, cx);
10223        editor.move_left(&MoveLeft, window, cx);
10224    });
10225    cx.assert_editor_state(
10226        &"
10227            ˇ)}
10228            ˇ)}
10229            ˇ)}
10230        "
10231        .unindent(),
10232    );
10233
10234    // skip-over closing brackets at multiple cursors
10235    cx.update_editor(|editor, window, cx| {
10236        editor.handle_input(")", window, cx);
10237        editor.handle_input("}", window, cx);
10238    });
10239    cx.assert_editor_state(
10240        &"
10241            )}ˇ
10242            )}ˇ
10243            )}ˇ
10244        "
10245        .unindent(),
10246    );
10247
10248    // ignore non-close brackets
10249    cx.update_editor(|editor, window, cx| {
10250        editor.handle_input("]", window, cx);
10251        editor.move_left(&MoveLeft, window, cx);
10252        editor.handle_input("]", window, cx);
10253    });
10254    cx.assert_editor_state(
10255        &"
10256            )}]ˇ]
10257            )}]ˇ]
10258            )}]ˇ]
10259        "
10260        .unindent(),
10261    );
10262}
10263
10264#[gpui::test]
10265async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
10266    init_test(cx, |_| {});
10267
10268    let mut cx = EditorTestContext::new(cx).await;
10269
10270    let html_language = Arc::new(
10271        Language::new(
10272            LanguageConfig {
10273                name: "HTML".into(),
10274                brackets: BracketPairConfig {
10275                    pairs: vec![
10276                        BracketPair {
10277                            start: "<".into(),
10278                            end: ">".into(),
10279                            close: true,
10280                            ..Default::default()
10281                        },
10282                        BracketPair {
10283                            start: "{".into(),
10284                            end: "}".into(),
10285                            close: true,
10286                            ..Default::default()
10287                        },
10288                        BracketPair {
10289                            start: "(".into(),
10290                            end: ")".into(),
10291                            close: true,
10292                            ..Default::default()
10293                        },
10294                    ],
10295                    ..Default::default()
10296                },
10297                autoclose_before: "})]>".into(),
10298                ..Default::default()
10299            },
10300            Some(tree_sitter_html::LANGUAGE.into()),
10301        )
10302        .with_injection_query(
10303            r#"
10304            (script_element
10305                (raw_text) @injection.content
10306                (#set! injection.language "javascript"))
10307            "#,
10308        )
10309        .unwrap(),
10310    );
10311
10312    let javascript_language = Arc::new(Language::new(
10313        LanguageConfig {
10314            name: "JavaScript".into(),
10315            brackets: BracketPairConfig {
10316                pairs: vec![
10317                    BracketPair {
10318                        start: "/*".into(),
10319                        end: " */".into(),
10320                        close: true,
10321                        ..Default::default()
10322                    },
10323                    BracketPair {
10324                        start: "{".into(),
10325                        end: "}".into(),
10326                        close: true,
10327                        ..Default::default()
10328                    },
10329                    BracketPair {
10330                        start: "(".into(),
10331                        end: ")".into(),
10332                        close: true,
10333                        ..Default::default()
10334                    },
10335                ],
10336                ..Default::default()
10337            },
10338            autoclose_before: "})]>".into(),
10339            ..Default::default()
10340        },
10341        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10342    ));
10343
10344    cx.language_registry().add(html_language.clone());
10345    cx.language_registry().add(javascript_language);
10346    cx.executor().run_until_parked();
10347
10348    cx.update_buffer(|buffer, cx| {
10349        buffer.set_language(Some(html_language), cx);
10350    });
10351
10352    cx.set_state(
10353        &r#"
10354            <body>ˇ
10355                <script>
10356                    var x = 1;ˇ
10357                </script>
10358            </body>ˇ
10359        "#
10360        .unindent(),
10361    );
10362
10363    // Precondition: different languages are active at different locations.
10364    cx.update_editor(|editor, window, cx| {
10365        let snapshot = editor.snapshot(window, cx);
10366        let cursors = editor
10367            .selections
10368            .ranges::<usize>(&editor.display_snapshot(cx));
10369        let languages = cursors
10370            .iter()
10371            .map(|c| snapshot.language_at(c.start).unwrap().name())
10372            .collect::<Vec<_>>();
10373        assert_eq!(
10374            languages,
10375            &["HTML".into(), "JavaScript".into(), "HTML".into()]
10376        );
10377    });
10378
10379    // Angle brackets autoclose in HTML, but not JavaScript.
10380    cx.update_editor(|editor, window, cx| {
10381        editor.handle_input("<", window, cx);
10382        editor.handle_input("a", window, cx);
10383    });
10384    cx.assert_editor_state(
10385        &r#"
10386            <body><aˇ>
10387                <script>
10388                    var x = 1;<aˇ
10389                </script>
10390            </body><aˇ>
10391        "#
10392        .unindent(),
10393    );
10394
10395    // Curly braces and parens autoclose in both HTML and JavaScript.
10396    cx.update_editor(|editor, window, cx| {
10397        editor.handle_input(" b=", window, cx);
10398        editor.handle_input("{", window, cx);
10399        editor.handle_input("c", window, cx);
10400        editor.handle_input("(", window, cx);
10401    });
10402    cx.assert_editor_state(
10403        &r#"
10404            <body><a b={c(ˇ)}>
10405                <script>
10406                    var x = 1;<a b={c(ˇ)}
10407                </script>
10408            </body><a b={c(ˇ)}>
10409        "#
10410        .unindent(),
10411    );
10412
10413    // Brackets that were already autoclosed are skipped.
10414    cx.update_editor(|editor, window, cx| {
10415        editor.handle_input(")", window, cx);
10416        editor.handle_input("d", window, cx);
10417        editor.handle_input("}", window, cx);
10418    });
10419    cx.assert_editor_state(
10420        &r#"
10421            <body><a b={c()d}ˇ>
10422                <script>
10423                    var x = 1;<a b={c()d}ˇ
10424                </script>
10425            </body><a b={c()d}ˇ>
10426        "#
10427        .unindent(),
10428    );
10429    cx.update_editor(|editor, window, cx| {
10430        editor.handle_input(">", window, cx);
10431    });
10432    cx.assert_editor_state(
10433        &r#"
10434            <body><a b={c()d}>ˇ
10435                <script>
10436                    var x = 1;<a b={c()d}>ˇ
10437                </script>
10438            </body><a b={c()d}>ˇ
10439        "#
10440        .unindent(),
10441    );
10442
10443    // Reset
10444    cx.set_state(
10445        &r#"
10446            <body>ˇ
10447                <script>
10448                    var x = 1;ˇ
10449                </script>
10450            </body>ˇ
10451        "#
10452        .unindent(),
10453    );
10454
10455    cx.update_editor(|editor, window, cx| {
10456        editor.handle_input("<", window, cx);
10457    });
10458    cx.assert_editor_state(
10459        &r#"
10460            <body><ˇ>
10461                <script>
10462                    var x = 1;<ˇ
10463                </script>
10464            </body><ˇ>
10465        "#
10466        .unindent(),
10467    );
10468
10469    // When backspacing, the closing angle brackets are removed.
10470    cx.update_editor(|editor, window, cx| {
10471        editor.backspace(&Backspace, window, cx);
10472    });
10473    cx.assert_editor_state(
10474        &r#"
10475            <body>ˇ
10476                <script>
10477                    var x = 1;ˇ
10478                </script>
10479            </body>ˇ
10480        "#
10481        .unindent(),
10482    );
10483
10484    // Block comments autoclose in JavaScript, but not HTML.
10485    cx.update_editor(|editor, window, cx| {
10486        editor.handle_input("/", window, cx);
10487        editor.handle_input("*", window, cx);
10488    });
10489    cx.assert_editor_state(
10490        &r#"
10491            <body>/*ˇ
10492                <script>
10493                    var x = 1;/*ˇ */
10494                </script>
10495            </body>/*ˇ
10496        "#
10497        .unindent(),
10498    );
10499}
10500
10501#[gpui::test]
10502async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
10503    init_test(cx, |_| {});
10504
10505    let mut cx = EditorTestContext::new(cx).await;
10506
10507    let rust_language = Arc::new(
10508        Language::new(
10509            LanguageConfig {
10510                name: "Rust".into(),
10511                brackets: serde_json::from_value(json!([
10512                    { "start": "{", "end": "}", "close": true, "newline": true },
10513                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
10514                ]))
10515                .unwrap(),
10516                autoclose_before: "})]>".into(),
10517                ..Default::default()
10518            },
10519            Some(tree_sitter_rust::LANGUAGE.into()),
10520        )
10521        .with_override_query("(string_literal) @string")
10522        .unwrap(),
10523    );
10524
10525    cx.language_registry().add(rust_language.clone());
10526    cx.update_buffer(|buffer, cx| {
10527        buffer.set_language(Some(rust_language), cx);
10528    });
10529
10530    cx.set_state(
10531        &r#"
10532            let x = ˇ
10533        "#
10534        .unindent(),
10535    );
10536
10537    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
10538    cx.update_editor(|editor, window, cx| {
10539        editor.handle_input("\"", window, cx);
10540    });
10541    cx.assert_editor_state(
10542        &r#"
10543            let x = "ˇ"
10544        "#
10545        .unindent(),
10546    );
10547
10548    // Inserting another quotation mark. The cursor moves across the existing
10549    // automatically-inserted quotation mark.
10550    cx.update_editor(|editor, window, cx| {
10551        editor.handle_input("\"", window, cx);
10552    });
10553    cx.assert_editor_state(
10554        &r#"
10555            let x = ""ˇ
10556        "#
10557        .unindent(),
10558    );
10559
10560    // Reset
10561    cx.set_state(
10562        &r#"
10563            let x = ˇ
10564        "#
10565        .unindent(),
10566    );
10567
10568    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
10569    cx.update_editor(|editor, window, cx| {
10570        editor.handle_input("\"", window, cx);
10571        editor.handle_input(" ", window, cx);
10572        editor.move_left(&Default::default(), window, cx);
10573        editor.handle_input("\\", window, cx);
10574        editor.handle_input("\"", window, cx);
10575    });
10576    cx.assert_editor_state(
10577        &r#"
10578            let x = "\"ˇ "
10579        "#
10580        .unindent(),
10581    );
10582
10583    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
10584    // mark. Nothing is inserted.
10585    cx.update_editor(|editor, window, cx| {
10586        editor.move_right(&Default::default(), window, cx);
10587        editor.handle_input("\"", window, cx);
10588    });
10589    cx.assert_editor_state(
10590        &r#"
10591            let x = "\" "ˇ
10592        "#
10593        .unindent(),
10594    );
10595}
10596
10597#[gpui::test]
10598async fn test_surround_with_pair(cx: &mut TestAppContext) {
10599    init_test(cx, |_| {});
10600
10601    let language = Arc::new(Language::new(
10602        LanguageConfig {
10603            brackets: BracketPairConfig {
10604                pairs: vec![
10605                    BracketPair {
10606                        start: "{".to_string(),
10607                        end: "}".to_string(),
10608                        close: true,
10609                        surround: true,
10610                        newline: true,
10611                    },
10612                    BracketPair {
10613                        start: "/* ".to_string(),
10614                        end: "*/".to_string(),
10615                        close: true,
10616                        surround: true,
10617                        ..Default::default()
10618                    },
10619                ],
10620                ..Default::default()
10621            },
10622            ..Default::default()
10623        },
10624        Some(tree_sitter_rust::LANGUAGE.into()),
10625    ));
10626
10627    let text = r#"
10628        a
10629        b
10630        c
10631    "#
10632    .unindent();
10633
10634    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10635    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10636    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10637    editor
10638        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10639        .await;
10640
10641    editor.update_in(cx, |editor, window, cx| {
10642        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10643            s.select_display_ranges([
10644                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10645                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10646                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
10647            ])
10648        });
10649
10650        editor.handle_input("{", window, cx);
10651        editor.handle_input("{", window, cx);
10652        editor.handle_input("{", window, cx);
10653        assert_eq!(
10654            editor.text(cx),
10655            "
10656                {{{a}}}
10657                {{{b}}}
10658                {{{c}}}
10659            "
10660            .unindent()
10661        );
10662        assert_eq!(
10663            display_ranges(editor, cx),
10664            [
10665                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
10666                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
10667                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
10668            ]
10669        );
10670
10671        editor.undo(&Undo, window, cx);
10672        editor.undo(&Undo, window, cx);
10673        editor.undo(&Undo, window, cx);
10674        assert_eq!(
10675            editor.text(cx),
10676            "
10677                a
10678                b
10679                c
10680            "
10681            .unindent()
10682        );
10683        assert_eq!(
10684            display_ranges(editor, cx),
10685            [
10686                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10687                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10688                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10689            ]
10690        );
10691
10692        // Ensure inserting the first character of a multi-byte bracket pair
10693        // doesn't surround the selections with the bracket.
10694        editor.handle_input("/", window, cx);
10695        assert_eq!(
10696            editor.text(cx),
10697            "
10698                /
10699                /
10700                /
10701            "
10702            .unindent()
10703        );
10704        assert_eq!(
10705            display_ranges(editor, cx),
10706            [
10707                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10708                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10709                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10710            ]
10711        );
10712
10713        editor.undo(&Undo, window, cx);
10714        assert_eq!(
10715            editor.text(cx),
10716            "
10717                a
10718                b
10719                c
10720            "
10721            .unindent()
10722        );
10723        assert_eq!(
10724            display_ranges(editor, cx),
10725            [
10726                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10727                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10728                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10729            ]
10730        );
10731
10732        // Ensure inserting the last character of a multi-byte bracket pair
10733        // doesn't surround the selections with the bracket.
10734        editor.handle_input("*", window, cx);
10735        assert_eq!(
10736            editor.text(cx),
10737            "
10738                *
10739                *
10740                *
10741            "
10742            .unindent()
10743        );
10744        assert_eq!(
10745            display_ranges(editor, cx),
10746            [
10747                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10748                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10749                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10750            ]
10751        );
10752    });
10753}
10754
10755#[gpui::test]
10756async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
10757    init_test(cx, |_| {});
10758
10759    let language = Arc::new(Language::new(
10760        LanguageConfig {
10761            brackets: BracketPairConfig {
10762                pairs: vec![BracketPair {
10763                    start: "{".to_string(),
10764                    end: "}".to_string(),
10765                    close: true,
10766                    surround: true,
10767                    newline: true,
10768                }],
10769                ..Default::default()
10770            },
10771            autoclose_before: "}".to_string(),
10772            ..Default::default()
10773        },
10774        Some(tree_sitter_rust::LANGUAGE.into()),
10775    ));
10776
10777    let text = r#"
10778        a
10779        b
10780        c
10781    "#
10782    .unindent();
10783
10784    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10785    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10786    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10787    editor
10788        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10789        .await;
10790
10791    editor.update_in(cx, |editor, window, cx| {
10792        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10793            s.select_ranges([
10794                Point::new(0, 1)..Point::new(0, 1),
10795                Point::new(1, 1)..Point::new(1, 1),
10796                Point::new(2, 1)..Point::new(2, 1),
10797            ])
10798        });
10799
10800        editor.handle_input("{", window, cx);
10801        editor.handle_input("{", window, cx);
10802        editor.handle_input("_", window, cx);
10803        assert_eq!(
10804            editor.text(cx),
10805            "
10806                a{{_}}
10807                b{{_}}
10808                c{{_}}
10809            "
10810            .unindent()
10811        );
10812        assert_eq!(
10813            editor
10814                .selections
10815                .ranges::<Point>(&editor.display_snapshot(cx)),
10816            [
10817                Point::new(0, 4)..Point::new(0, 4),
10818                Point::new(1, 4)..Point::new(1, 4),
10819                Point::new(2, 4)..Point::new(2, 4)
10820            ]
10821        );
10822
10823        editor.backspace(&Default::default(), window, cx);
10824        editor.backspace(&Default::default(), window, cx);
10825        assert_eq!(
10826            editor.text(cx),
10827            "
10828                a{}
10829                b{}
10830                c{}
10831            "
10832            .unindent()
10833        );
10834        assert_eq!(
10835            editor
10836                .selections
10837                .ranges::<Point>(&editor.display_snapshot(cx)),
10838            [
10839                Point::new(0, 2)..Point::new(0, 2),
10840                Point::new(1, 2)..Point::new(1, 2),
10841                Point::new(2, 2)..Point::new(2, 2)
10842            ]
10843        );
10844
10845        editor.delete_to_previous_word_start(&Default::default(), window, cx);
10846        assert_eq!(
10847            editor.text(cx),
10848            "
10849                a
10850                b
10851                c
10852            "
10853            .unindent()
10854        );
10855        assert_eq!(
10856            editor
10857                .selections
10858                .ranges::<Point>(&editor.display_snapshot(cx)),
10859            [
10860                Point::new(0, 1)..Point::new(0, 1),
10861                Point::new(1, 1)..Point::new(1, 1),
10862                Point::new(2, 1)..Point::new(2, 1)
10863            ]
10864        );
10865    });
10866}
10867
10868#[gpui::test]
10869async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
10870    init_test(cx, |settings| {
10871        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10872    });
10873
10874    let mut cx = EditorTestContext::new(cx).await;
10875
10876    let language = Arc::new(Language::new(
10877        LanguageConfig {
10878            brackets: BracketPairConfig {
10879                pairs: vec![
10880                    BracketPair {
10881                        start: "{".to_string(),
10882                        end: "}".to_string(),
10883                        close: true,
10884                        surround: true,
10885                        newline: true,
10886                    },
10887                    BracketPair {
10888                        start: "(".to_string(),
10889                        end: ")".to_string(),
10890                        close: true,
10891                        surround: true,
10892                        newline: true,
10893                    },
10894                    BracketPair {
10895                        start: "[".to_string(),
10896                        end: "]".to_string(),
10897                        close: false,
10898                        surround: true,
10899                        newline: true,
10900                    },
10901                ],
10902                ..Default::default()
10903            },
10904            autoclose_before: "})]".to_string(),
10905            ..Default::default()
10906        },
10907        Some(tree_sitter_rust::LANGUAGE.into()),
10908    ));
10909
10910    cx.language_registry().add(language.clone());
10911    cx.update_buffer(|buffer, cx| {
10912        buffer.set_language(Some(language), cx);
10913    });
10914
10915    cx.set_state(
10916        &"
10917            {(ˇ)}
10918            [[ˇ]]
10919            {(ˇ)}
10920        "
10921        .unindent(),
10922    );
10923
10924    cx.update_editor(|editor, window, cx| {
10925        editor.backspace(&Default::default(), window, cx);
10926        editor.backspace(&Default::default(), window, cx);
10927    });
10928
10929    cx.assert_editor_state(
10930        &"
10931            ˇ
10932            ˇ]]
10933            ˇ
10934        "
10935        .unindent(),
10936    );
10937
10938    cx.update_editor(|editor, window, cx| {
10939        editor.handle_input("{", window, cx);
10940        editor.handle_input("{", window, cx);
10941        editor.move_right(&MoveRight, window, cx);
10942        editor.move_right(&MoveRight, window, cx);
10943        editor.move_left(&MoveLeft, window, cx);
10944        editor.move_left(&MoveLeft, window, cx);
10945        editor.backspace(&Default::default(), window, cx);
10946    });
10947
10948    cx.assert_editor_state(
10949        &"
10950            {ˇ}
10951            {ˇ}]]
10952            {ˇ}
10953        "
10954        .unindent(),
10955    );
10956
10957    cx.update_editor(|editor, window, cx| {
10958        editor.backspace(&Default::default(), window, cx);
10959    });
10960
10961    cx.assert_editor_state(
10962        &"
10963            ˇ
10964            ˇ]]
10965            ˇ
10966        "
10967        .unindent(),
10968    );
10969}
10970
10971#[gpui::test]
10972async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
10973    init_test(cx, |_| {});
10974
10975    let language = Arc::new(Language::new(
10976        LanguageConfig::default(),
10977        Some(tree_sitter_rust::LANGUAGE.into()),
10978    ));
10979
10980    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
10981    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10982    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10983    editor
10984        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10985        .await;
10986
10987    editor.update_in(cx, |editor, window, cx| {
10988        editor.set_auto_replace_emoji_shortcode(true);
10989
10990        editor.handle_input("Hello ", window, cx);
10991        editor.handle_input(":wave", window, cx);
10992        assert_eq!(editor.text(cx), "Hello :wave".unindent());
10993
10994        editor.handle_input(":", window, cx);
10995        assert_eq!(editor.text(cx), "Hello 👋".unindent());
10996
10997        editor.handle_input(" :smile", window, cx);
10998        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
10999
11000        editor.handle_input(":", window, cx);
11001        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
11002
11003        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
11004        editor.handle_input(":wave", window, cx);
11005        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
11006
11007        editor.handle_input(":", window, cx);
11008        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
11009
11010        editor.handle_input(":1", window, cx);
11011        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
11012
11013        editor.handle_input(":", window, cx);
11014        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
11015
11016        // Ensure shortcode does not get replaced when it is part of a word
11017        editor.handle_input(" Test:wave", window, cx);
11018        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
11019
11020        editor.handle_input(":", window, cx);
11021        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
11022
11023        editor.set_auto_replace_emoji_shortcode(false);
11024
11025        // Ensure shortcode does not get replaced when auto replace is off
11026        editor.handle_input(" :wave", window, cx);
11027        assert_eq!(
11028            editor.text(cx),
11029            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
11030        );
11031
11032        editor.handle_input(":", window, cx);
11033        assert_eq!(
11034            editor.text(cx),
11035            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
11036        );
11037    });
11038}
11039
11040#[gpui::test]
11041async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
11042    init_test(cx, |_| {});
11043
11044    let (text, insertion_ranges) = marked_text_ranges(
11045        indoc! {"
11046            ˇ
11047        "},
11048        false,
11049    );
11050
11051    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
11052    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11053
11054    _ = editor.update_in(cx, |editor, window, cx| {
11055        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
11056
11057        editor
11058            .insert_snippet(&insertion_ranges, snippet, window, cx)
11059            .unwrap();
11060
11061        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
11062            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
11063            assert_eq!(editor.text(cx), expected_text);
11064            assert_eq!(
11065                editor
11066                    .selections
11067                    .ranges::<usize>(&editor.display_snapshot(cx)),
11068                selection_ranges
11069            );
11070        }
11071
11072        assert(
11073            editor,
11074            cx,
11075            indoc! {"
11076            type «» =•
11077            "},
11078        );
11079
11080        assert!(editor.context_menu_visible(), "There should be a matches");
11081    });
11082}
11083
11084#[gpui::test]
11085async fn test_snippet_tabstop_navigation_with_placeholders(cx: &mut TestAppContext) {
11086    init_test(cx, |_| {});
11087
11088    fn assert_state(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
11089        let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
11090        assert_eq!(editor.text(cx), expected_text);
11091        assert_eq!(
11092            editor
11093                .selections
11094                .ranges::<usize>(&editor.display_snapshot(cx)),
11095            selection_ranges
11096        );
11097    }
11098
11099    let (text, insertion_ranges) = marked_text_ranges(
11100        indoc! {"
11101            ˇ
11102        "},
11103        false,
11104    );
11105
11106    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
11107    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11108
11109    _ = editor.update_in(cx, |editor, window, cx| {
11110        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2; $3").unwrap();
11111
11112        editor
11113            .insert_snippet(&insertion_ranges, snippet, window, cx)
11114            .unwrap();
11115
11116        assert_state(
11117            editor,
11118            cx,
11119            indoc! {"
11120            type «» = ;•
11121            "},
11122        );
11123
11124        assert!(
11125            editor.context_menu_visible(),
11126            "Context menu should be visible for placeholder choices"
11127        );
11128
11129        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11130
11131        assert_state(
11132            editor,
11133            cx,
11134            indoc! {"
11135            type  = «»;•
11136            "},
11137        );
11138
11139        assert!(
11140            !editor.context_menu_visible(),
11141            "Context menu should be hidden after moving to next tabstop"
11142        );
11143
11144        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11145
11146        assert_state(
11147            editor,
11148            cx,
11149            indoc! {"
11150            type  = ; ˇ
11151            "},
11152        );
11153
11154        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11155
11156        assert_state(
11157            editor,
11158            cx,
11159            indoc! {"
11160            type  = ; ˇ
11161            "},
11162        );
11163    });
11164
11165    _ = editor.update_in(cx, |editor, window, cx| {
11166        editor.select_all(&SelectAll, window, cx);
11167        editor.backspace(&Backspace, window, cx);
11168
11169        let snippet = Snippet::parse("fn ${1|,foo,bar|} = ${2:value}; $3").unwrap();
11170        let insertion_ranges = editor
11171            .selections
11172            .all(&editor.display_snapshot(cx))
11173            .iter()
11174            .map(|s| s.range())
11175            .collect::<Vec<_>>();
11176
11177        editor
11178            .insert_snippet(&insertion_ranges, snippet, window, cx)
11179            .unwrap();
11180
11181        assert_state(editor, cx, "fn «» = value;•");
11182
11183        assert!(
11184            editor.context_menu_visible(),
11185            "Context menu should be visible for placeholder choices"
11186        );
11187
11188        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11189
11190        assert_state(editor, cx, "fn  = «valueˇ»;•");
11191
11192        editor.previous_snippet_tabstop(&PreviousSnippetTabstop, window, cx);
11193
11194        assert_state(editor, cx, "fn «» = value;•");
11195
11196        assert!(
11197            editor.context_menu_visible(),
11198            "Context menu should be visible again after returning to first tabstop"
11199        );
11200
11201        editor.previous_snippet_tabstop(&PreviousSnippetTabstop, window, cx);
11202
11203        assert_state(editor, cx, "fn «» = value;•");
11204    });
11205}
11206
11207#[gpui::test]
11208async fn test_snippets(cx: &mut TestAppContext) {
11209    init_test(cx, |_| {});
11210
11211    let mut cx = EditorTestContext::new(cx).await;
11212
11213    cx.set_state(indoc! {"
11214        a.ˇ b
11215        a.ˇ b
11216        a.ˇ b
11217    "});
11218
11219    cx.update_editor(|editor, window, cx| {
11220        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
11221        let insertion_ranges = editor
11222            .selections
11223            .all(&editor.display_snapshot(cx))
11224            .iter()
11225            .map(|s| s.range())
11226            .collect::<Vec<_>>();
11227        editor
11228            .insert_snippet(&insertion_ranges, snippet, window, cx)
11229            .unwrap();
11230    });
11231
11232    cx.assert_editor_state(indoc! {"
11233        a.f(«oneˇ», two, «threeˇ») b
11234        a.f(«oneˇ», two, «threeˇ») b
11235        a.f(«oneˇ», two, «threeˇ») b
11236    "});
11237
11238    // Can't move earlier than the first tab stop
11239    cx.update_editor(|editor, window, cx| {
11240        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
11241    });
11242    cx.assert_editor_state(indoc! {"
11243        a.f(«oneˇ», two, «threeˇ») b
11244        a.f(«oneˇ», two, «threeˇ») b
11245        a.f(«oneˇ», two, «threeˇ») b
11246    "});
11247
11248    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11249    cx.assert_editor_state(indoc! {"
11250        a.f(one, «twoˇ», three) b
11251        a.f(one, «twoˇ», three) b
11252        a.f(one, «twoˇ», three) b
11253    "});
11254
11255    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
11256    cx.assert_editor_state(indoc! {"
11257        a.f(«oneˇ», two, «threeˇ») b
11258        a.f(«oneˇ», two, «threeˇ») b
11259        a.f(«oneˇ», two, «threeˇ») b
11260    "});
11261
11262    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11263    cx.assert_editor_state(indoc! {"
11264        a.f(one, «twoˇ», three) b
11265        a.f(one, «twoˇ», three) b
11266        a.f(one, «twoˇ», three) b
11267    "});
11268    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11269    cx.assert_editor_state(indoc! {"
11270        a.f(one, two, three)ˇ b
11271        a.f(one, two, three)ˇ b
11272        a.f(one, two, three)ˇ b
11273    "});
11274
11275    // As soon as the last tab stop is reached, snippet state is gone
11276    cx.update_editor(|editor, window, cx| {
11277        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
11278    });
11279    cx.assert_editor_state(indoc! {"
11280        a.f(one, two, three)ˇ b
11281        a.f(one, two, three)ˇ b
11282        a.f(one, two, three)ˇ b
11283    "});
11284}
11285
11286#[gpui::test]
11287async fn test_snippet_indentation(cx: &mut TestAppContext) {
11288    init_test(cx, |_| {});
11289
11290    let mut cx = EditorTestContext::new(cx).await;
11291
11292    cx.update_editor(|editor, window, cx| {
11293        let snippet = Snippet::parse(indoc! {"
11294            /*
11295             * Multiline comment with leading indentation
11296             *
11297             * $1
11298             */
11299            $0"})
11300        .unwrap();
11301        let insertion_ranges = editor
11302            .selections
11303            .all(&editor.display_snapshot(cx))
11304            .iter()
11305            .map(|s| s.range())
11306            .collect::<Vec<_>>();
11307        editor
11308            .insert_snippet(&insertion_ranges, snippet, window, cx)
11309            .unwrap();
11310    });
11311
11312    cx.assert_editor_state(indoc! {"
11313        /*
11314         * Multiline comment with leading indentation
11315         *
11316         * ˇ
11317         */
11318    "});
11319
11320    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11321    cx.assert_editor_state(indoc! {"
11322        /*
11323         * Multiline comment with leading indentation
11324         *
11325         *•
11326         */
11327        ˇ"});
11328}
11329
11330#[gpui::test]
11331async fn test_document_format_during_save(cx: &mut TestAppContext) {
11332    init_test(cx, |_| {});
11333
11334    let fs = FakeFs::new(cx.executor());
11335    fs.insert_file(path!("/file.rs"), Default::default()).await;
11336
11337    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
11338
11339    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11340    language_registry.add(rust_lang());
11341    let mut fake_servers = language_registry.register_fake_lsp(
11342        "Rust",
11343        FakeLspAdapter {
11344            capabilities: lsp::ServerCapabilities {
11345                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11346                ..Default::default()
11347            },
11348            ..Default::default()
11349        },
11350    );
11351
11352    let buffer = project
11353        .update(cx, |project, cx| {
11354            project.open_local_buffer(path!("/file.rs"), cx)
11355        })
11356        .await
11357        .unwrap();
11358
11359    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11360    let (editor, cx) = cx.add_window_view(|window, cx| {
11361        build_editor_with_project(project.clone(), buffer, window, cx)
11362    });
11363    editor.update_in(cx, |editor, window, cx| {
11364        editor.set_text("one\ntwo\nthree\n", window, cx)
11365    });
11366    assert!(cx.read(|cx| editor.is_dirty(cx)));
11367
11368    cx.executor().start_waiting();
11369    let fake_server = fake_servers.next().await.unwrap();
11370
11371    {
11372        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11373            move |params, _| async move {
11374                assert_eq!(
11375                    params.text_document.uri,
11376                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11377                );
11378                assert_eq!(params.options.tab_size, 4);
11379                Ok(Some(vec![lsp::TextEdit::new(
11380                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11381                    ", ".to_string(),
11382                )]))
11383            },
11384        );
11385        let save = editor
11386            .update_in(cx, |editor, window, cx| {
11387                editor.save(
11388                    SaveOptions {
11389                        format: true,
11390                        autosave: false,
11391                    },
11392                    project.clone(),
11393                    window,
11394                    cx,
11395                )
11396            })
11397            .unwrap();
11398        cx.executor().start_waiting();
11399        save.await;
11400
11401        assert_eq!(
11402            editor.update(cx, |editor, cx| editor.text(cx)),
11403            "one, two\nthree\n"
11404        );
11405        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11406    }
11407
11408    {
11409        editor.update_in(cx, |editor, window, cx| {
11410            editor.set_text("one\ntwo\nthree\n", window, cx)
11411        });
11412        assert!(cx.read(|cx| editor.is_dirty(cx)));
11413
11414        // Ensure we can still save even if formatting hangs.
11415        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11416            move |params, _| async move {
11417                assert_eq!(
11418                    params.text_document.uri,
11419                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11420                );
11421                futures::future::pending::<()>().await;
11422                unreachable!()
11423            },
11424        );
11425        let save = editor
11426            .update_in(cx, |editor, window, cx| {
11427                editor.save(
11428                    SaveOptions {
11429                        format: true,
11430                        autosave: false,
11431                    },
11432                    project.clone(),
11433                    window,
11434                    cx,
11435                )
11436            })
11437            .unwrap();
11438        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11439        cx.executor().start_waiting();
11440        save.await;
11441        assert_eq!(
11442            editor.update(cx, |editor, cx| editor.text(cx)),
11443            "one\ntwo\nthree\n"
11444        );
11445    }
11446
11447    // Set rust language override and assert overridden tabsize is sent to language server
11448    update_test_language_settings(cx, |settings| {
11449        settings.languages.0.insert(
11450            "Rust".into(),
11451            LanguageSettingsContent {
11452                tab_size: NonZeroU32::new(8),
11453                ..Default::default()
11454            },
11455        );
11456    });
11457
11458    {
11459        editor.update_in(cx, |editor, window, cx| {
11460            editor.set_text("somehting_new\n", window, cx)
11461        });
11462        assert!(cx.read(|cx| editor.is_dirty(cx)));
11463        let _formatting_request_signal = fake_server
11464            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
11465                assert_eq!(
11466                    params.text_document.uri,
11467                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11468                );
11469                assert_eq!(params.options.tab_size, 8);
11470                Ok(Some(vec![]))
11471            });
11472        let save = editor
11473            .update_in(cx, |editor, window, cx| {
11474                editor.save(
11475                    SaveOptions {
11476                        format: true,
11477                        autosave: false,
11478                    },
11479                    project.clone(),
11480                    window,
11481                    cx,
11482                )
11483            })
11484            .unwrap();
11485        cx.executor().start_waiting();
11486        save.await;
11487    }
11488}
11489
11490#[gpui::test]
11491async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
11492    init_test(cx, |settings| {
11493        settings.defaults.ensure_final_newline_on_save = Some(false);
11494    });
11495
11496    let fs = FakeFs::new(cx.executor());
11497    fs.insert_file(path!("/file.txt"), "foo".into()).await;
11498
11499    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
11500
11501    let buffer = project
11502        .update(cx, |project, cx| {
11503            project.open_local_buffer(path!("/file.txt"), cx)
11504        })
11505        .await
11506        .unwrap();
11507
11508    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11509    let (editor, cx) = cx.add_window_view(|window, cx| {
11510        build_editor_with_project(project.clone(), buffer, window, cx)
11511    });
11512    editor.update_in(cx, |editor, window, cx| {
11513        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
11514            s.select_ranges([0..0])
11515        });
11516    });
11517    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11518
11519    editor.update_in(cx, |editor, window, cx| {
11520        editor.handle_input("\n", window, cx)
11521    });
11522    cx.run_until_parked();
11523    save(&editor, &project, cx).await;
11524    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11525
11526    editor.update_in(cx, |editor, window, cx| {
11527        editor.undo(&Default::default(), window, cx);
11528    });
11529    save(&editor, &project, cx).await;
11530    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11531
11532    editor.update_in(cx, |editor, window, cx| {
11533        editor.redo(&Default::default(), window, cx);
11534    });
11535    cx.run_until_parked();
11536    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11537
11538    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
11539        let save = editor
11540            .update_in(cx, |editor, window, cx| {
11541                editor.save(
11542                    SaveOptions {
11543                        format: true,
11544                        autosave: false,
11545                    },
11546                    project.clone(),
11547                    window,
11548                    cx,
11549                )
11550            })
11551            .unwrap();
11552        cx.executor().start_waiting();
11553        save.await;
11554        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11555    }
11556}
11557
11558#[gpui::test]
11559async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
11560    init_test(cx, |_| {});
11561
11562    let cols = 4;
11563    let rows = 10;
11564    let sample_text_1 = sample_text(rows, cols, 'a');
11565    assert_eq!(
11566        sample_text_1,
11567        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11568    );
11569    let sample_text_2 = sample_text(rows, cols, 'l');
11570    assert_eq!(
11571        sample_text_2,
11572        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11573    );
11574    let sample_text_3 = sample_text(rows, cols, 'v');
11575    assert_eq!(
11576        sample_text_3,
11577        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11578    );
11579
11580    let fs = FakeFs::new(cx.executor());
11581    fs.insert_tree(
11582        path!("/a"),
11583        json!({
11584            "main.rs": sample_text_1,
11585            "other.rs": sample_text_2,
11586            "lib.rs": sample_text_3,
11587        }),
11588    )
11589    .await;
11590
11591    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11592    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11593    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11594
11595    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11596    language_registry.add(rust_lang());
11597    let mut fake_servers = language_registry.register_fake_lsp(
11598        "Rust",
11599        FakeLspAdapter {
11600            capabilities: lsp::ServerCapabilities {
11601                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11602                ..Default::default()
11603            },
11604            ..Default::default()
11605        },
11606    );
11607
11608    let worktree = project.update(cx, |project, cx| {
11609        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
11610        assert_eq!(worktrees.len(), 1);
11611        worktrees.pop().unwrap()
11612    });
11613    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11614
11615    let buffer_1 = project
11616        .update(cx, |project, cx| {
11617            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
11618        })
11619        .await
11620        .unwrap();
11621    let buffer_2 = project
11622        .update(cx, |project, cx| {
11623            project.open_buffer((worktree_id, rel_path("other.rs")), cx)
11624        })
11625        .await
11626        .unwrap();
11627    let buffer_3 = project
11628        .update(cx, |project, cx| {
11629            project.open_buffer((worktree_id, rel_path("lib.rs")), cx)
11630        })
11631        .await
11632        .unwrap();
11633
11634    let multi_buffer = cx.new(|cx| {
11635        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11636        multi_buffer.push_excerpts(
11637            buffer_1.clone(),
11638            [
11639                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11640                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11641                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11642            ],
11643            cx,
11644        );
11645        multi_buffer.push_excerpts(
11646            buffer_2.clone(),
11647            [
11648                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11649                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11650                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11651            ],
11652            cx,
11653        );
11654        multi_buffer.push_excerpts(
11655            buffer_3.clone(),
11656            [
11657                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11658                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11659                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11660            ],
11661            cx,
11662        );
11663        multi_buffer
11664    });
11665    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
11666        Editor::new(
11667            EditorMode::full(),
11668            multi_buffer,
11669            Some(project.clone()),
11670            window,
11671            cx,
11672        )
11673    });
11674
11675    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11676        editor.change_selections(
11677            SelectionEffects::scroll(Autoscroll::Next),
11678            window,
11679            cx,
11680            |s| s.select_ranges(Some(1..2)),
11681        );
11682        editor.insert("|one|two|three|", window, cx);
11683    });
11684    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11685    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11686        editor.change_selections(
11687            SelectionEffects::scroll(Autoscroll::Next),
11688            window,
11689            cx,
11690            |s| s.select_ranges(Some(60..70)),
11691        );
11692        editor.insert("|four|five|six|", window, cx);
11693    });
11694    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11695
11696    // First two buffers should be edited, but not the third one.
11697    assert_eq!(
11698        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11699        "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}",
11700    );
11701    buffer_1.update(cx, |buffer, _| {
11702        assert!(buffer.is_dirty());
11703        assert_eq!(
11704            buffer.text(),
11705            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
11706        )
11707    });
11708    buffer_2.update(cx, |buffer, _| {
11709        assert!(buffer.is_dirty());
11710        assert_eq!(
11711            buffer.text(),
11712            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
11713        )
11714    });
11715    buffer_3.update(cx, |buffer, _| {
11716        assert!(!buffer.is_dirty());
11717        assert_eq!(buffer.text(), sample_text_3,)
11718    });
11719    cx.executor().run_until_parked();
11720
11721    cx.executor().start_waiting();
11722    let save = multi_buffer_editor
11723        .update_in(cx, |editor, window, cx| {
11724            editor.save(
11725                SaveOptions {
11726                    format: true,
11727                    autosave: false,
11728                },
11729                project.clone(),
11730                window,
11731                cx,
11732            )
11733        })
11734        .unwrap();
11735
11736    let fake_server = fake_servers.next().await.unwrap();
11737    fake_server
11738        .server
11739        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
11740            Ok(Some(vec![lsp::TextEdit::new(
11741                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11742                format!("[{} formatted]", params.text_document.uri),
11743            )]))
11744        })
11745        .detach();
11746    save.await;
11747
11748    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
11749    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
11750    assert_eq!(
11751        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11752        uri!(
11753            "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}"
11754        ),
11755    );
11756    buffer_1.update(cx, |buffer, _| {
11757        assert!(!buffer.is_dirty());
11758        assert_eq!(
11759            buffer.text(),
11760            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
11761        )
11762    });
11763    buffer_2.update(cx, |buffer, _| {
11764        assert!(!buffer.is_dirty());
11765        assert_eq!(
11766            buffer.text(),
11767            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
11768        )
11769    });
11770    buffer_3.update(cx, |buffer, _| {
11771        assert!(!buffer.is_dirty());
11772        assert_eq!(buffer.text(), sample_text_3,)
11773    });
11774}
11775
11776#[gpui::test]
11777async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
11778    init_test(cx, |_| {});
11779
11780    let fs = FakeFs::new(cx.executor());
11781    fs.insert_tree(
11782        path!("/dir"),
11783        json!({
11784            "file1.rs": "fn main() { println!(\"hello\"); }",
11785            "file2.rs": "fn test() { println!(\"test\"); }",
11786            "file3.rs": "fn other() { println!(\"other\"); }\n",
11787        }),
11788    )
11789    .await;
11790
11791    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
11792    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11793    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11794
11795    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11796    language_registry.add(rust_lang());
11797
11798    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
11799    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11800
11801    // Open three buffers
11802    let buffer_1 = project
11803        .update(cx, |project, cx| {
11804            project.open_buffer((worktree_id, rel_path("file1.rs")), cx)
11805        })
11806        .await
11807        .unwrap();
11808    let buffer_2 = project
11809        .update(cx, |project, cx| {
11810            project.open_buffer((worktree_id, rel_path("file2.rs")), cx)
11811        })
11812        .await
11813        .unwrap();
11814    let buffer_3 = project
11815        .update(cx, |project, cx| {
11816            project.open_buffer((worktree_id, rel_path("file3.rs")), cx)
11817        })
11818        .await
11819        .unwrap();
11820
11821    // Create a multi-buffer with all three buffers
11822    let multi_buffer = cx.new(|cx| {
11823        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11824        multi_buffer.push_excerpts(
11825            buffer_1.clone(),
11826            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11827            cx,
11828        );
11829        multi_buffer.push_excerpts(
11830            buffer_2.clone(),
11831            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11832            cx,
11833        );
11834        multi_buffer.push_excerpts(
11835            buffer_3.clone(),
11836            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11837            cx,
11838        );
11839        multi_buffer
11840    });
11841
11842    let editor = cx.new_window_entity(|window, cx| {
11843        Editor::new(
11844            EditorMode::full(),
11845            multi_buffer,
11846            Some(project.clone()),
11847            window,
11848            cx,
11849        )
11850    });
11851
11852    // Edit only the first buffer
11853    editor.update_in(cx, |editor, window, cx| {
11854        editor.change_selections(
11855            SelectionEffects::scroll(Autoscroll::Next),
11856            window,
11857            cx,
11858            |s| s.select_ranges(Some(10..10)),
11859        );
11860        editor.insert("// edited", window, cx);
11861    });
11862
11863    // Verify that only buffer 1 is dirty
11864    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
11865    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11866    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11867
11868    // Get write counts after file creation (files were created with initial content)
11869    // We expect each file to have been written once during creation
11870    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
11871    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
11872    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
11873
11874    // Perform autosave
11875    let save_task = editor.update_in(cx, |editor, window, cx| {
11876        editor.save(
11877            SaveOptions {
11878                format: true,
11879                autosave: true,
11880            },
11881            project.clone(),
11882            window,
11883            cx,
11884        )
11885    });
11886    save_task.await.unwrap();
11887
11888    // Only the dirty buffer should have been saved
11889    assert_eq!(
11890        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11891        1,
11892        "Buffer 1 was dirty, so it should have been written once during autosave"
11893    );
11894    assert_eq!(
11895        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11896        0,
11897        "Buffer 2 was clean, so it should not have been written during autosave"
11898    );
11899    assert_eq!(
11900        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11901        0,
11902        "Buffer 3 was clean, so it should not have been written during autosave"
11903    );
11904
11905    // Verify buffer states after autosave
11906    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11907    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11908    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11909
11910    // Now perform a manual save (format = true)
11911    let save_task = editor.update_in(cx, |editor, window, cx| {
11912        editor.save(
11913            SaveOptions {
11914                format: true,
11915                autosave: false,
11916            },
11917            project.clone(),
11918            window,
11919            cx,
11920        )
11921    });
11922    save_task.await.unwrap();
11923
11924    // During manual save, clean buffers don't get written to disk
11925    // They just get did_save called for language server notifications
11926    assert_eq!(
11927        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11928        1,
11929        "Buffer 1 should only have been written once total (during autosave, not manual save)"
11930    );
11931    assert_eq!(
11932        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11933        0,
11934        "Buffer 2 should not have been written at all"
11935    );
11936    assert_eq!(
11937        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11938        0,
11939        "Buffer 3 should not have been written at all"
11940    );
11941}
11942
11943async fn setup_range_format_test(
11944    cx: &mut TestAppContext,
11945) -> (
11946    Entity<Project>,
11947    Entity<Editor>,
11948    &mut gpui::VisualTestContext,
11949    lsp::FakeLanguageServer,
11950) {
11951    init_test(cx, |_| {});
11952
11953    let fs = FakeFs::new(cx.executor());
11954    fs.insert_file(path!("/file.rs"), Default::default()).await;
11955
11956    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11957
11958    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11959    language_registry.add(rust_lang());
11960    let mut fake_servers = language_registry.register_fake_lsp(
11961        "Rust",
11962        FakeLspAdapter {
11963            capabilities: lsp::ServerCapabilities {
11964                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
11965                ..lsp::ServerCapabilities::default()
11966            },
11967            ..FakeLspAdapter::default()
11968        },
11969    );
11970
11971    let buffer = project
11972        .update(cx, |project, cx| {
11973            project.open_local_buffer(path!("/file.rs"), cx)
11974        })
11975        .await
11976        .unwrap();
11977
11978    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11979    let (editor, cx) = cx.add_window_view(|window, cx| {
11980        build_editor_with_project(project.clone(), buffer, window, cx)
11981    });
11982
11983    cx.executor().start_waiting();
11984    let fake_server = fake_servers.next().await.unwrap();
11985
11986    (project, editor, cx, fake_server)
11987}
11988
11989#[gpui::test]
11990async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
11991    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11992
11993    editor.update_in(cx, |editor, window, cx| {
11994        editor.set_text("one\ntwo\nthree\n", window, cx)
11995    });
11996    assert!(cx.read(|cx| editor.is_dirty(cx)));
11997
11998    let save = editor
11999        .update_in(cx, |editor, window, cx| {
12000            editor.save(
12001                SaveOptions {
12002                    format: true,
12003                    autosave: false,
12004                },
12005                project.clone(),
12006                window,
12007                cx,
12008            )
12009        })
12010        .unwrap();
12011    fake_server
12012        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
12013            assert_eq!(
12014                params.text_document.uri,
12015                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12016            );
12017            assert_eq!(params.options.tab_size, 4);
12018            Ok(Some(vec![lsp::TextEdit::new(
12019                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12020                ", ".to_string(),
12021            )]))
12022        })
12023        .next()
12024        .await;
12025    cx.executor().start_waiting();
12026    save.await;
12027    assert_eq!(
12028        editor.update(cx, |editor, cx| editor.text(cx)),
12029        "one, two\nthree\n"
12030    );
12031    assert!(!cx.read(|cx| editor.is_dirty(cx)));
12032}
12033
12034#[gpui::test]
12035async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
12036    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12037
12038    editor.update_in(cx, |editor, window, cx| {
12039        editor.set_text("one\ntwo\nthree\n", window, cx)
12040    });
12041    assert!(cx.read(|cx| editor.is_dirty(cx)));
12042
12043    // Test that save still works when formatting hangs
12044    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
12045        move |params, _| async move {
12046            assert_eq!(
12047                params.text_document.uri,
12048                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12049            );
12050            futures::future::pending::<()>().await;
12051            unreachable!()
12052        },
12053    );
12054    let save = editor
12055        .update_in(cx, |editor, window, cx| {
12056            editor.save(
12057                SaveOptions {
12058                    format: true,
12059                    autosave: false,
12060                },
12061                project.clone(),
12062                window,
12063                cx,
12064            )
12065        })
12066        .unwrap();
12067    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12068    cx.executor().start_waiting();
12069    save.await;
12070    assert_eq!(
12071        editor.update(cx, |editor, cx| editor.text(cx)),
12072        "one\ntwo\nthree\n"
12073    );
12074    assert!(!cx.read(|cx| editor.is_dirty(cx)));
12075}
12076
12077#[gpui::test]
12078async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
12079    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12080
12081    // Buffer starts clean, no formatting should be requested
12082    let save = editor
12083        .update_in(cx, |editor, window, cx| {
12084            editor.save(
12085                SaveOptions {
12086                    format: false,
12087                    autosave: false,
12088                },
12089                project.clone(),
12090                window,
12091                cx,
12092            )
12093        })
12094        .unwrap();
12095    let _pending_format_request = fake_server
12096        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
12097            panic!("Should not be invoked");
12098        })
12099        .next();
12100    cx.executor().start_waiting();
12101    save.await;
12102    cx.run_until_parked();
12103}
12104
12105#[gpui::test]
12106async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
12107    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12108
12109    // Set Rust language override and assert overridden tabsize is sent to language server
12110    update_test_language_settings(cx, |settings| {
12111        settings.languages.0.insert(
12112            "Rust".into(),
12113            LanguageSettingsContent {
12114                tab_size: NonZeroU32::new(8),
12115                ..Default::default()
12116            },
12117        );
12118    });
12119
12120    editor.update_in(cx, |editor, window, cx| {
12121        editor.set_text("something_new\n", window, cx)
12122    });
12123    assert!(cx.read(|cx| editor.is_dirty(cx)));
12124    let save = editor
12125        .update_in(cx, |editor, window, cx| {
12126            editor.save(
12127                SaveOptions {
12128                    format: true,
12129                    autosave: false,
12130                },
12131                project.clone(),
12132                window,
12133                cx,
12134            )
12135        })
12136        .unwrap();
12137    fake_server
12138        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
12139            assert_eq!(
12140                params.text_document.uri,
12141                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12142            );
12143            assert_eq!(params.options.tab_size, 8);
12144            Ok(Some(Vec::new()))
12145        })
12146        .next()
12147        .await;
12148    save.await;
12149}
12150
12151#[gpui::test]
12152async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
12153    init_test(cx, |settings| {
12154        settings.defaults.formatter = Some(FormatterList::Single(Formatter::LanguageServer(
12155            settings::LanguageServerFormatterSpecifier::Current,
12156        )))
12157    });
12158
12159    let fs = FakeFs::new(cx.executor());
12160    fs.insert_file(path!("/file.rs"), Default::default()).await;
12161
12162    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12163
12164    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12165    language_registry.add(Arc::new(Language::new(
12166        LanguageConfig {
12167            name: "Rust".into(),
12168            matcher: LanguageMatcher {
12169                path_suffixes: vec!["rs".to_string()],
12170                ..Default::default()
12171            },
12172            ..LanguageConfig::default()
12173        },
12174        Some(tree_sitter_rust::LANGUAGE.into()),
12175    )));
12176    update_test_language_settings(cx, |settings| {
12177        // Enable Prettier formatting for the same buffer, and ensure
12178        // LSP is called instead of Prettier.
12179        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12180    });
12181    let mut fake_servers = language_registry.register_fake_lsp(
12182        "Rust",
12183        FakeLspAdapter {
12184            capabilities: lsp::ServerCapabilities {
12185                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12186                ..Default::default()
12187            },
12188            ..Default::default()
12189        },
12190    );
12191
12192    let buffer = project
12193        .update(cx, |project, cx| {
12194            project.open_local_buffer(path!("/file.rs"), cx)
12195        })
12196        .await
12197        .unwrap();
12198
12199    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12200    let (editor, cx) = cx.add_window_view(|window, cx| {
12201        build_editor_with_project(project.clone(), buffer, window, cx)
12202    });
12203    editor.update_in(cx, |editor, window, cx| {
12204        editor.set_text("one\ntwo\nthree\n", window, cx)
12205    });
12206
12207    cx.executor().start_waiting();
12208    let fake_server = fake_servers.next().await.unwrap();
12209
12210    let format = editor
12211        .update_in(cx, |editor, window, cx| {
12212            editor.perform_format(
12213                project.clone(),
12214                FormatTrigger::Manual,
12215                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12216                window,
12217                cx,
12218            )
12219        })
12220        .unwrap();
12221    fake_server
12222        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
12223            assert_eq!(
12224                params.text_document.uri,
12225                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12226            );
12227            assert_eq!(params.options.tab_size, 4);
12228            Ok(Some(vec![lsp::TextEdit::new(
12229                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12230                ", ".to_string(),
12231            )]))
12232        })
12233        .next()
12234        .await;
12235    cx.executor().start_waiting();
12236    format.await;
12237    assert_eq!(
12238        editor.update(cx, |editor, cx| editor.text(cx)),
12239        "one, two\nthree\n"
12240    );
12241
12242    editor.update_in(cx, |editor, window, cx| {
12243        editor.set_text("one\ntwo\nthree\n", window, cx)
12244    });
12245    // Ensure we don't lock if formatting hangs.
12246    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12247        move |params, _| async move {
12248            assert_eq!(
12249                params.text_document.uri,
12250                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12251            );
12252            futures::future::pending::<()>().await;
12253            unreachable!()
12254        },
12255    );
12256    let format = editor
12257        .update_in(cx, |editor, window, cx| {
12258            editor.perform_format(
12259                project,
12260                FormatTrigger::Manual,
12261                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12262                window,
12263                cx,
12264            )
12265        })
12266        .unwrap();
12267    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12268    cx.executor().start_waiting();
12269    format.await;
12270    assert_eq!(
12271        editor.update(cx, |editor, cx| editor.text(cx)),
12272        "one\ntwo\nthree\n"
12273    );
12274}
12275
12276#[gpui::test]
12277async fn test_multiple_formatters(cx: &mut TestAppContext) {
12278    init_test(cx, |settings| {
12279        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
12280        settings.defaults.formatter = Some(FormatterList::Vec(vec![
12281            Formatter::LanguageServer(settings::LanguageServerFormatterSpecifier::Current),
12282            Formatter::CodeAction("code-action-1".into()),
12283            Formatter::CodeAction("code-action-2".into()),
12284        ]))
12285    });
12286
12287    let fs = FakeFs::new(cx.executor());
12288    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
12289        .await;
12290
12291    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12292    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12293    language_registry.add(rust_lang());
12294
12295    let mut fake_servers = language_registry.register_fake_lsp(
12296        "Rust",
12297        FakeLspAdapter {
12298            capabilities: lsp::ServerCapabilities {
12299                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12300                execute_command_provider: Some(lsp::ExecuteCommandOptions {
12301                    commands: vec!["the-command-for-code-action-1".into()],
12302                    ..Default::default()
12303                }),
12304                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12305                ..Default::default()
12306            },
12307            ..Default::default()
12308        },
12309    );
12310
12311    let buffer = project
12312        .update(cx, |project, cx| {
12313            project.open_local_buffer(path!("/file.rs"), cx)
12314        })
12315        .await
12316        .unwrap();
12317
12318    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12319    let (editor, cx) = cx.add_window_view(|window, cx| {
12320        build_editor_with_project(project.clone(), buffer, window, cx)
12321    });
12322
12323    cx.executor().start_waiting();
12324
12325    let fake_server = fake_servers.next().await.unwrap();
12326    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12327        move |_params, _| async move {
12328            Ok(Some(vec![lsp::TextEdit::new(
12329                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12330                "applied-formatting\n".to_string(),
12331            )]))
12332        },
12333    );
12334    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12335        move |params, _| async move {
12336            let requested_code_actions = params.context.only.expect("Expected code action request");
12337            assert_eq!(requested_code_actions.len(), 1);
12338
12339            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
12340            let code_action = match requested_code_actions[0].as_str() {
12341                "code-action-1" => lsp::CodeAction {
12342                    kind: Some("code-action-1".into()),
12343                    edit: Some(lsp::WorkspaceEdit::new(
12344                        [(
12345                            uri,
12346                            vec![lsp::TextEdit::new(
12347                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12348                                "applied-code-action-1-edit\n".to_string(),
12349                            )],
12350                        )]
12351                        .into_iter()
12352                        .collect(),
12353                    )),
12354                    command: Some(lsp::Command {
12355                        command: "the-command-for-code-action-1".into(),
12356                        ..Default::default()
12357                    }),
12358                    ..Default::default()
12359                },
12360                "code-action-2" => lsp::CodeAction {
12361                    kind: Some("code-action-2".into()),
12362                    edit: Some(lsp::WorkspaceEdit::new(
12363                        [(
12364                            uri,
12365                            vec![lsp::TextEdit::new(
12366                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12367                                "applied-code-action-2-edit\n".to_string(),
12368                            )],
12369                        )]
12370                        .into_iter()
12371                        .collect(),
12372                    )),
12373                    ..Default::default()
12374                },
12375                req => panic!("Unexpected code action request: {:?}", req),
12376            };
12377            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12378                code_action,
12379            )]))
12380        },
12381    );
12382
12383    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
12384        move |params, _| async move { Ok(params) }
12385    });
12386
12387    let command_lock = Arc::new(futures::lock::Mutex::new(()));
12388    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
12389        let fake = fake_server.clone();
12390        let lock = command_lock.clone();
12391        move |params, _| {
12392            assert_eq!(params.command, "the-command-for-code-action-1");
12393            let fake = fake.clone();
12394            let lock = lock.clone();
12395            async move {
12396                lock.lock().await;
12397                fake.server
12398                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
12399                        label: None,
12400                        edit: lsp::WorkspaceEdit {
12401                            changes: Some(
12402                                [(
12403                                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
12404                                    vec![lsp::TextEdit {
12405                                        range: lsp::Range::new(
12406                                            lsp::Position::new(0, 0),
12407                                            lsp::Position::new(0, 0),
12408                                        ),
12409                                        new_text: "applied-code-action-1-command\n".into(),
12410                                    }],
12411                                )]
12412                                .into_iter()
12413                                .collect(),
12414                            ),
12415                            ..Default::default()
12416                        },
12417                    })
12418                    .await
12419                    .into_response()
12420                    .unwrap();
12421                Ok(Some(json!(null)))
12422            }
12423        }
12424    });
12425
12426    cx.executor().start_waiting();
12427    editor
12428        .update_in(cx, |editor, window, cx| {
12429            editor.perform_format(
12430                project.clone(),
12431                FormatTrigger::Manual,
12432                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12433                window,
12434                cx,
12435            )
12436        })
12437        .unwrap()
12438        .await;
12439    editor.update(cx, |editor, cx| {
12440        assert_eq!(
12441            editor.text(cx),
12442            r#"
12443                applied-code-action-2-edit
12444                applied-code-action-1-command
12445                applied-code-action-1-edit
12446                applied-formatting
12447                one
12448                two
12449                three
12450            "#
12451            .unindent()
12452        );
12453    });
12454
12455    editor.update_in(cx, |editor, window, cx| {
12456        editor.undo(&Default::default(), window, cx);
12457        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12458    });
12459
12460    // Perform a manual edit while waiting for an LSP command
12461    // that's being run as part of a formatting code action.
12462    let lock_guard = command_lock.lock().await;
12463    let format = editor
12464        .update_in(cx, |editor, window, cx| {
12465            editor.perform_format(
12466                project.clone(),
12467                FormatTrigger::Manual,
12468                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12469                window,
12470                cx,
12471            )
12472        })
12473        .unwrap();
12474    cx.run_until_parked();
12475    editor.update(cx, |editor, cx| {
12476        assert_eq!(
12477            editor.text(cx),
12478            r#"
12479                applied-code-action-1-edit
12480                applied-formatting
12481                one
12482                two
12483                three
12484            "#
12485            .unindent()
12486        );
12487
12488        editor.buffer.update(cx, |buffer, cx| {
12489            let ix = buffer.len(cx);
12490            buffer.edit([(ix..ix, "edited\n")], None, cx);
12491        });
12492    });
12493
12494    // Allow the LSP command to proceed. Because the buffer was edited,
12495    // the second code action will not be run.
12496    drop(lock_guard);
12497    format.await;
12498    editor.update_in(cx, |editor, window, cx| {
12499        assert_eq!(
12500            editor.text(cx),
12501            r#"
12502                applied-code-action-1-command
12503                applied-code-action-1-edit
12504                applied-formatting
12505                one
12506                two
12507                three
12508                edited
12509            "#
12510            .unindent()
12511        );
12512
12513        // The manual edit is undone first, because it is the last thing the user did
12514        // (even though the command completed afterwards).
12515        editor.undo(&Default::default(), window, cx);
12516        assert_eq!(
12517            editor.text(cx),
12518            r#"
12519                applied-code-action-1-command
12520                applied-code-action-1-edit
12521                applied-formatting
12522                one
12523                two
12524                three
12525            "#
12526            .unindent()
12527        );
12528
12529        // All the formatting (including the command, which completed after the manual edit)
12530        // is undone together.
12531        editor.undo(&Default::default(), window, cx);
12532        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12533    });
12534}
12535
12536#[gpui::test]
12537async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
12538    init_test(cx, |settings| {
12539        settings.defaults.formatter = Some(FormatterList::Vec(vec![Formatter::LanguageServer(
12540            settings::LanguageServerFormatterSpecifier::Current,
12541        )]))
12542    });
12543
12544    let fs = FakeFs::new(cx.executor());
12545    fs.insert_file(path!("/file.ts"), Default::default()).await;
12546
12547    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12548
12549    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12550    language_registry.add(Arc::new(Language::new(
12551        LanguageConfig {
12552            name: "TypeScript".into(),
12553            matcher: LanguageMatcher {
12554                path_suffixes: vec!["ts".to_string()],
12555                ..Default::default()
12556            },
12557            ..LanguageConfig::default()
12558        },
12559        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12560    )));
12561    update_test_language_settings(cx, |settings| {
12562        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12563    });
12564    let mut fake_servers = language_registry.register_fake_lsp(
12565        "TypeScript",
12566        FakeLspAdapter {
12567            capabilities: lsp::ServerCapabilities {
12568                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12569                ..Default::default()
12570            },
12571            ..Default::default()
12572        },
12573    );
12574
12575    let buffer = project
12576        .update(cx, |project, cx| {
12577            project.open_local_buffer(path!("/file.ts"), cx)
12578        })
12579        .await
12580        .unwrap();
12581
12582    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12583    let (editor, cx) = cx.add_window_view(|window, cx| {
12584        build_editor_with_project(project.clone(), buffer, window, cx)
12585    });
12586    editor.update_in(cx, |editor, window, cx| {
12587        editor.set_text(
12588            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12589            window,
12590            cx,
12591        )
12592    });
12593
12594    cx.executor().start_waiting();
12595    let fake_server = fake_servers.next().await.unwrap();
12596
12597    let format = editor
12598        .update_in(cx, |editor, window, cx| {
12599            editor.perform_code_action_kind(
12600                project.clone(),
12601                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12602                window,
12603                cx,
12604            )
12605        })
12606        .unwrap();
12607    fake_server
12608        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
12609            assert_eq!(
12610                params.text_document.uri,
12611                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12612            );
12613            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12614                lsp::CodeAction {
12615                    title: "Organize Imports".to_string(),
12616                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
12617                    edit: Some(lsp::WorkspaceEdit {
12618                        changes: Some(
12619                            [(
12620                                params.text_document.uri.clone(),
12621                                vec![lsp::TextEdit::new(
12622                                    lsp::Range::new(
12623                                        lsp::Position::new(1, 0),
12624                                        lsp::Position::new(2, 0),
12625                                    ),
12626                                    "".to_string(),
12627                                )],
12628                            )]
12629                            .into_iter()
12630                            .collect(),
12631                        ),
12632                        ..Default::default()
12633                    }),
12634                    ..Default::default()
12635                },
12636            )]))
12637        })
12638        .next()
12639        .await;
12640    cx.executor().start_waiting();
12641    format.await;
12642    assert_eq!(
12643        editor.update(cx, |editor, cx| editor.text(cx)),
12644        "import { a } from 'module';\n\nconst x = a;\n"
12645    );
12646
12647    editor.update_in(cx, |editor, window, cx| {
12648        editor.set_text(
12649            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12650            window,
12651            cx,
12652        )
12653    });
12654    // Ensure we don't lock if code action hangs.
12655    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12656        move |params, _| async move {
12657            assert_eq!(
12658                params.text_document.uri,
12659                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12660            );
12661            futures::future::pending::<()>().await;
12662            unreachable!()
12663        },
12664    );
12665    let format = editor
12666        .update_in(cx, |editor, window, cx| {
12667            editor.perform_code_action_kind(
12668                project,
12669                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12670                window,
12671                cx,
12672            )
12673        })
12674        .unwrap();
12675    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
12676    cx.executor().start_waiting();
12677    format.await;
12678    assert_eq!(
12679        editor.update(cx, |editor, cx| editor.text(cx)),
12680        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
12681    );
12682}
12683
12684#[gpui::test]
12685async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
12686    init_test(cx, |_| {});
12687
12688    let mut cx = EditorLspTestContext::new_rust(
12689        lsp::ServerCapabilities {
12690            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12691            ..Default::default()
12692        },
12693        cx,
12694    )
12695    .await;
12696
12697    cx.set_state(indoc! {"
12698        one.twoˇ
12699    "});
12700
12701    // The format request takes a long time. When it completes, it inserts
12702    // a newline and an indent before the `.`
12703    cx.lsp
12704        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
12705            let executor = cx.background_executor().clone();
12706            async move {
12707                executor.timer(Duration::from_millis(100)).await;
12708                Ok(Some(vec![lsp::TextEdit {
12709                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
12710                    new_text: "\n    ".into(),
12711                }]))
12712            }
12713        });
12714
12715    // Submit a format request.
12716    let format_1 = cx
12717        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12718        .unwrap();
12719    cx.executor().run_until_parked();
12720
12721    // Submit a second format request.
12722    let format_2 = cx
12723        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12724        .unwrap();
12725    cx.executor().run_until_parked();
12726
12727    // Wait for both format requests to complete
12728    cx.executor().advance_clock(Duration::from_millis(200));
12729    cx.executor().start_waiting();
12730    format_1.await.unwrap();
12731    cx.executor().start_waiting();
12732    format_2.await.unwrap();
12733
12734    // The formatting edits only happens once.
12735    cx.assert_editor_state(indoc! {"
12736        one
12737            .twoˇ
12738    "});
12739}
12740
12741#[gpui::test]
12742async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
12743    init_test(cx, |settings| {
12744        settings.defaults.formatter = Some(FormatterList::default())
12745    });
12746
12747    let mut cx = EditorLspTestContext::new_rust(
12748        lsp::ServerCapabilities {
12749            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12750            ..Default::default()
12751        },
12752        cx,
12753    )
12754    .await;
12755
12756    // Record which buffer changes have been sent to the language server
12757    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
12758    cx.lsp
12759        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
12760            let buffer_changes = buffer_changes.clone();
12761            move |params, _| {
12762                buffer_changes.lock().extend(
12763                    params
12764                        .content_changes
12765                        .into_iter()
12766                        .map(|e| (e.range.unwrap(), e.text)),
12767                );
12768            }
12769        });
12770    // Handle formatting requests to the language server.
12771    cx.lsp
12772        .set_request_handler::<lsp::request::Formatting, _, _>({
12773            let buffer_changes = buffer_changes.clone();
12774            move |_, _| {
12775                let buffer_changes = buffer_changes.clone();
12776                // Insert blank lines between each line of the buffer.
12777                async move {
12778                    // When formatting is requested, trailing whitespace has already been stripped,
12779                    // and the trailing newline has already been added.
12780                    assert_eq!(
12781                        &buffer_changes.lock()[1..],
12782                        &[
12783                            (
12784                                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
12785                                "".into()
12786                            ),
12787                            (
12788                                lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
12789                                "".into()
12790                            ),
12791                            (
12792                                lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
12793                                "\n".into()
12794                            ),
12795                        ]
12796                    );
12797
12798                    Ok(Some(vec![
12799                        lsp::TextEdit {
12800                            range: lsp::Range::new(
12801                                lsp::Position::new(1, 0),
12802                                lsp::Position::new(1, 0),
12803                            ),
12804                            new_text: "\n".into(),
12805                        },
12806                        lsp::TextEdit {
12807                            range: lsp::Range::new(
12808                                lsp::Position::new(2, 0),
12809                                lsp::Position::new(2, 0),
12810                            ),
12811                            new_text: "\n".into(),
12812                        },
12813                    ]))
12814                }
12815            }
12816        });
12817
12818    // Set up a buffer white some trailing whitespace and no trailing newline.
12819    cx.set_state(
12820        &[
12821            "one ",   //
12822            "twoˇ",   //
12823            "three ", //
12824            "four",   //
12825        ]
12826        .join("\n"),
12827    );
12828    cx.run_until_parked();
12829
12830    // Submit a format request.
12831    let format = cx
12832        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12833        .unwrap();
12834
12835    cx.run_until_parked();
12836    // After formatting the buffer, the trailing whitespace is stripped,
12837    // a newline is appended, and the edits provided by the language server
12838    // have been applied.
12839    format.await.unwrap();
12840
12841    cx.assert_editor_state(
12842        &[
12843            "one",   //
12844            "",      //
12845            "twoˇ",  //
12846            "",      //
12847            "three", //
12848            "four",  //
12849            "",      //
12850        ]
12851        .join("\n"),
12852    );
12853
12854    // Undoing the formatting undoes the trailing whitespace removal, the
12855    // trailing newline, and the LSP edits.
12856    cx.update_buffer(|buffer, cx| buffer.undo(cx));
12857    cx.assert_editor_state(
12858        &[
12859            "one ",   //
12860            "twoˇ",   //
12861            "three ", //
12862            "four",   //
12863        ]
12864        .join("\n"),
12865    );
12866}
12867
12868#[gpui::test]
12869async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
12870    cx: &mut TestAppContext,
12871) {
12872    init_test(cx, |_| {});
12873
12874    cx.update(|cx| {
12875        cx.update_global::<SettingsStore, _>(|settings, cx| {
12876            settings.update_user_settings(cx, |settings| {
12877                settings.editor.auto_signature_help = Some(true);
12878            });
12879        });
12880    });
12881
12882    let mut cx = EditorLspTestContext::new_rust(
12883        lsp::ServerCapabilities {
12884            signature_help_provider: Some(lsp::SignatureHelpOptions {
12885                ..Default::default()
12886            }),
12887            ..Default::default()
12888        },
12889        cx,
12890    )
12891    .await;
12892
12893    let language = Language::new(
12894        LanguageConfig {
12895            name: "Rust".into(),
12896            brackets: BracketPairConfig {
12897                pairs: vec![
12898                    BracketPair {
12899                        start: "{".to_string(),
12900                        end: "}".to_string(),
12901                        close: true,
12902                        surround: true,
12903                        newline: true,
12904                    },
12905                    BracketPair {
12906                        start: "(".to_string(),
12907                        end: ")".to_string(),
12908                        close: true,
12909                        surround: true,
12910                        newline: true,
12911                    },
12912                    BracketPair {
12913                        start: "/*".to_string(),
12914                        end: " */".to_string(),
12915                        close: true,
12916                        surround: true,
12917                        newline: true,
12918                    },
12919                    BracketPair {
12920                        start: "[".to_string(),
12921                        end: "]".to_string(),
12922                        close: false,
12923                        surround: false,
12924                        newline: true,
12925                    },
12926                    BracketPair {
12927                        start: "\"".to_string(),
12928                        end: "\"".to_string(),
12929                        close: true,
12930                        surround: true,
12931                        newline: false,
12932                    },
12933                    BracketPair {
12934                        start: "<".to_string(),
12935                        end: ">".to_string(),
12936                        close: false,
12937                        surround: true,
12938                        newline: true,
12939                    },
12940                ],
12941                ..Default::default()
12942            },
12943            autoclose_before: "})]".to_string(),
12944            ..Default::default()
12945        },
12946        Some(tree_sitter_rust::LANGUAGE.into()),
12947    );
12948    let language = Arc::new(language);
12949
12950    cx.language_registry().add(language.clone());
12951    cx.update_buffer(|buffer, cx| {
12952        buffer.set_language(Some(language), cx);
12953    });
12954
12955    cx.set_state(
12956        &r#"
12957            fn main() {
12958                sampleˇ
12959            }
12960        "#
12961        .unindent(),
12962    );
12963
12964    cx.update_editor(|editor, window, cx| {
12965        editor.handle_input("(", window, cx);
12966    });
12967    cx.assert_editor_state(
12968        &"
12969            fn main() {
12970                sample(ˇ)
12971            }
12972        "
12973        .unindent(),
12974    );
12975
12976    let mocked_response = lsp::SignatureHelp {
12977        signatures: vec![lsp::SignatureInformation {
12978            label: "fn sample(param1: u8, param2: u8)".to_string(),
12979            documentation: None,
12980            parameters: Some(vec![
12981                lsp::ParameterInformation {
12982                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12983                    documentation: None,
12984                },
12985                lsp::ParameterInformation {
12986                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12987                    documentation: None,
12988                },
12989            ]),
12990            active_parameter: None,
12991        }],
12992        active_signature: Some(0),
12993        active_parameter: Some(0),
12994    };
12995    handle_signature_help_request(&mut cx, mocked_response).await;
12996
12997    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12998        .await;
12999
13000    cx.editor(|editor, _, _| {
13001        let signature_help_state = editor.signature_help_state.popover().cloned();
13002        let signature = signature_help_state.unwrap();
13003        assert_eq!(
13004            signature.signatures[signature.current_signature].label,
13005            "fn sample(param1: u8, param2: u8)"
13006        );
13007    });
13008}
13009
13010#[gpui::test]
13011async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
13012    init_test(cx, |_| {});
13013
13014    cx.update(|cx| {
13015        cx.update_global::<SettingsStore, _>(|settings, cx| {
13016            settings.update_user_settings(cx, |settings| {
13017                settings.editor.auto_signature_help = Some(false);
13018                settings.editor.show_signature_help_after_edits = Some(false);
13019            });
13020        });
13021    });
13022
13023    let mut cx = EditorLspTestContext::new_rust(
13024        lsp::ServerCapabilities {
13025            signature_help_provider: Some(lsp::SignatureHelpOptions {
13026                ..Default::default()
13027            }),
13028            ..Default::default()
13029        },
13030        cx,
13031    )
13032    .await;
13033
13034    let language = Language::new(
13035        LanguageConfig {
13036            name: "Rust".into(),
13037            brackets: BracketPairConfig {
13038                pairs: vec![
13039                    BracketPair {
13040                        start: "{".to_string(),
13041                        end: "}".to_string(),
13042                        close: true,
13043                        surround: true,
13044                        newline: true,
13045                    },
13046                    BracketPair {
13047                        start: "(".to_string(),
13048                        end: ")".to_string(),
13049                        close: true,
13050                        surround: true,
13051                        newline: true,
13052                    },
13053                    BracketPair {
13054                        start: "/*".to_string(),
13055                        end: " */".to_string(),
13056                        close: true,
13057                        surround: true,
13058                        newline: true,
13059                    },
13060                    BracketPair {
13061                        start: "[".to_string(),
13062                        end: "]".to_string(),
13063                        close: false,
13064                        surround: false,
13065                        newline: true,
13066                    },
13067                    BracketPair {
13068                        start: "\"".to_string(),
13069                        end: "\"".to_string(),
13070                        close: true,
13071                        surround: true,
13072                        newline: false,
13073                    },
13074                    BracketPair {
13075                        start: "<".to_string(),
13076                        end: ">".to_string(),
13077                        close: false,
13078                        surround: true,
13079                        newline: true,
13080                    },
13081                ],
13082                ..Default::default()
13083            },
13084            autoclose_before: "})]".to_string(),
13085            ..Default::default()
13086        },
13087        Some(tree_sitter_rust::LANGUAGE.into()),
13088    );
13089    let language = Arc::new(language);
13090
13091    cx.language_registry().add(language.clone());
13092    cx.update_buffer(|buffer, cx| {
13093        buffer.set_language(Some(language), cx);
13094    });
13095
13096    // Ensure that signature_help is not called when no signature help is enabled.
13097    cx.set_state(
13098        &r#"
13099            fn main() {
13100                sampleˇ
13101            }
13102        "#
13103        .unindent(),
13104    );
13105    cx.update_editor(|editor, window, cx| {
13106        editor.handle_input("(", window, cx);
13107    });
13108    cx.assert_editor_state(
13109        &"
13110            fn main() {
13111                sample(ˇ)
13112            }
13113        "
13114        .unindent(),
13115    );
13116    cx.editor(|editor, _, _| {
13117        assert!(editor.signature_help_state.task().is_none());
13118    });
13119
13120    let mocked_response = lsp::SignatureHelp {
13121        signatures: vec![lsp::SignatureInformation {
13122            label: "fn sample(param1: u8, param2: u8)".to_string(),
13123            documentation: None,
13124            parameters: Some(vec![
13125                lsp::ParameterInformation {
13126                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13127                    documentation: None,
13128                },
13129                lsp::ParameterInformation {
13130                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13131                    documentation: None,
13132                },
13133            ]),
13134            active_parameter: None,
13135        }],
13136        active_signature: Some(0),
13137        active_parameter: Some(0),
13138    };
13139
13140    // Ensure that signature_help is called when enabled afte edits
13141    cx.update(|_, cx| {
13142        cx.update_global::<SettingsStore, _>(|settings, cx| {
13143            settings.update_user_settings(cx, |settings| {
13144                settings.editor.auto_signature_help = Some(false);
13145                settings.editor.show_signature_help_after_edits = Some(true);
13146            });
13147        });
13148    });
13149    cx.set_state(
13150        &r#"
13151            fn main() {
13152                sampleˇ
13153            }
13154        "#
13155        .unindent(),
13156    );
13157    cx.update_editor(|editor, window, cx| {
13158        editor.handle_input("(", window, cx);
13159    });
13160    cx.assert_editor_state(
13161        &"
13162            fn main() {
13163                sample(ˇ)
13164            }
13165        "
13166        .unindent(),
13167    );
13168    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13169    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13170        .await;
13171    cx.update_editor(|editor, _, _| {
13172        let signature_help_state = editor.signature_help_state.popover().cloned();
13173        assert!(signature_help_state.is_some());
13174        let signature = signature_help_state.unwrap();
13175        assert_eq!(
13176            signature.signatures[signature.current_signature].label,
13177            "fn sample(param1: u8, param2: u8)"
13178        );
13179        editor.signature_help_state = SignatureHelpState::default();
13180    });
13181
13182    // Ensure that signature_help is called when auto signature help override is enabled
13183    cx.update(|_, cx| {
13184        cx.update_global::<SettingsStore, _>(|settings, cx| {
13185            settings.update_user_settings(cx, |settings| {
13186                settings.editor.auto_signature_help = Some(true);
13187                settings.editor.show_signature_help_after_edits = Some(false);
13188            });
13189        });
13190    });
13191    cx.set_state(
13192        &r#"
13193            fn main() {
13194                sampleˇ
13195            }
13196        "#
13197        .unindent(),
13198    );
13199    cx.update_editor(|editor, window, cx| {
13200        editor.handle_input("(", window, cx);
13201    });
13202    cx.assert_editor_state(
13203        &"
13204            fn main() {
13205                sample(ˇ)
13206            }
13207        "
13208        .unindent(),
13209    );
13210    handle_signature_help_request(&mut cx, mocked_response).await;
13211    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13212        .await;
13213    cx.editor(|editor, _, _| {
13214        let signature_help_state = editor.signature_help_state.popover().cloned();
13215        assert!(signature_help_state.is_some());
13216        let signature = signature_help_state.unwrap();
13217        assert_eq!(
13218            signature.signatures[signature.current_signature].label,
13219            "fn sample(param1: u8, param2: u8)"
13220        );
13221    });
13222}
13223
13224#[gpui::test]
13225async fn test_signature_help(cx: &mut TestAppContext) {
13226    init_test(cx, |_| {});
13227    cx.update(|cx| {
13228        cx.update_global::<SettingsStore, _>(|settings, cx| {
13229            settings.update_user_settings(cx, |settings| {
13230                settings.editor.auto_signature_help = Some(true);
13231            });
13232        });
13233    });
13234
13235    let mut cx = EditorLspTestContext::new_rust(
13236        lsp::ServerCapabilities {
13237            signature_help_provider: Some(lsp::SignatureHelpOptions {
13238                ..Default::default()
13239            }),
13240            ..Default::default()
13241        },
13242        cx,
13243    )
13244    .await;
13245
13246    // A test that directly calls `show_signature_help`
13247    cx.update_editor(|editor, window, cx| {
13248        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13249    });
13250
13251    let mocked_response = lsp::SignatureHelp {
13252        signatures: vec![lsp::SignatureInformation {
13253            label: "fn sample(param1: u8, param2: u8)".to_string(),
13254            documentation: None,
13255            parameters: Some(vec![
13256                lsp::ParameterInformation {
13257                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13258                    documentation: None,
13259                },
13260                lsp::ParameterInformation {
13261                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13262                    documentation: None,
13263                },
13264            ]),
13265            active_parameter: None,
13266        }],
13267        active_signature: Some(0),
13268        active_parameter: Some(0),
13269    };
13270    handle_signature_help_request(&mut cx, mocked_response).await;
13271
13272    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13273        .await;
13274
13275    cx.editor(|editor, _, _| {
13276        let signature_help_state = editor.signature_help_state.popover().cloned();
13277        assert!(signature_help_state.is_some());
13278        let signature = signature_help_state.unwrap();
13279        assert_eq!(
13280            signature.signatures[signature.current_signature].label,
13281            "fn sample(param1: u8, param2: u8)"
13282        );
13283    });
13284
13285    // When exiting outside from inside the brackets, `signature_help` is closed.
13286    cx.set_state(indoc! {"
13287        fn main() {
13288            sample(ˇ);
13289        }
13290
13291        fn sample(param1: u8, param2: u8) {}
13292    "});
13293
13294    cx.update_editor(|editor, window, cx| {
13295        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13296            s.select_ranges([0..0])
13297        });
13298    });
13299
13300    let mocked_response = lsp::SignatureHelp {
13301        signatures: Vec::new(),
13302        active_signature: None,
13303        active_parameter: None,
13304    };
13305    handle_signature_help_request(&mut cx, mocked_response).await;
13306
13307    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13308        .await;
13309
13310    cx.editor(|editor, _, _| {
13311        assert!(!editor.signature_help_state.is_shown());
13312    });
13313
13314    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
13315    cx.set_state(indoc! {"
13316        fn main() {
13317            sample(ˇ);
13318        }
13319
13320        fn sample(param1: u8, param2: u8) {}
13321    "});
13322
13323    let mocked_response = lsp::SignatureHelp {
13324        signatures: vec![lsp::SignatureInformation {
13325            label: "fn sample(param1: u8, param2: u8)".to_string(),
13326            documentation: None,
13327            parameters: Some(vec![
13328                lsp::ParameterInformation {
13329                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13330                    documentation: None,
13331                },
13332                lsp::ParameterInformation {
13333                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13334                    documentation: None,
13335                },
13336            ]),
13337            active_parameter: None,
13338        }],
13339        active_signature: Some(0),
13340        active_parameter: Some(0),
13341    };
13342    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13343    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13344        .await;
13345    cx.editor(|editor, _, _| {
13346        assert!(editor.signature_help_state.is_shown());
13347    });
13348
13349    // Restore the popover with more parameter input
13350    cx.set_state(indoc! {"
13351        fn main() {
13352            sample(param1, param2ˇ);
13353        }
13354
13355        fn sample(param1: u8, param2: u8) {}
13356    "});
13357
13358    let mocked_response = lsp::SignatureHelp {
13359        signatures: vec![lsp::SignatureInformation {
13360            label: "fn sample(param1: u8, param2: u8)".to_string(),
13361            documentation: None,
13362            parameters: Some(vec![
13363                lsp::ParameterInformation {
13364                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13365                    documentation: None,
13366                },
13367                lsp::ParameterInformation {
13368                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13369                    documentation: None,
13370                },
13371            ]),
13372            active_parameter: None,
13373        }],
13374        active_signature: Some(0),
13375        active_parameter: Some(1),
13376    };
13377    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13378    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13379        .await;
13380
13381    // When selecting a range, the popover is gone.
13382    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
13383    cx.update_editor(|editor, window, cx| {
13384        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13385            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13386        })
13387    });
13388    cx.assert_editor_state(indoc! {"
13389        fn main() {
13390            sample(param1, «ˇparam2»);
13391        }
13392
13393        fn sample(param1: u8, param2: u8) {}
13394    "});
13395    cx.editor(|editor, _, _| {
13396        assert!(!editor.signature_help_state.is_shown());
13397    });
13398
13399    // When unselecting again, the popover is back if within the brackets.
13400    cx.update_editor(|editor, window, cx| {
13401        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13402            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13403        })
13404    });
13405    cx.assert_editor_state(indoc! {"
13406        fn main() {
13407            sample(param1, ˇparam2);
13408        }
13409
13410        fn sample(param1: u8, param2: u8) {}
13411    "});
13412    handle_signature_help_request(&mut cx, mocked_response).await;
13413    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13414        .await;
13415    cx.editor(|editor, _, _| {
13416        assert!(editor.signature_help_state.is_shown());
13417    });
13418
13419    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
13420    cx.update_editor(|editor, window, cx| {
13421        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13422            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
13423            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13424        })
13425    });
13426    cx.assert_editor_state(indoc! {"
13427        fn main() {
13428            sample(param1, ˇparam2);
13429        }
13430
13431        fn sample(param1: u8, param2: u8) {}
13432    "});
13433
13434    let mocked_response = lsp::SignatureHelp {
13435        signatures: vec![lsp::SignatureInformation {
13436            label: "fn sample(param1: u8, param2: u8)".to_string(),
13437            documentation: None,
13438            parameters: Some(vec![
13439                lsp::ParameterInformation {
13440                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13441                    documentation: None,
13442                },
13443                lsp::ParameterInformation {
13444                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13445                    documentation: None,
13446                },
13447            ]),
13448            active_parameter: None,
13449        }],
13450        active_signature: Some(0),
13451        active_parameter: Some(1),
13452    };
13453    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13454    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13455        .await;
13456    cx.update_editor(|editor, _, cx| {
13457        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
13458    });
13459    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13460        .await;
13461    cx.update_editor(|editor, window, cx| {
13462        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13463            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13464        })
13465    });
13466    cx.assert_editor_state(indoc! {"
13467        fn main() {
13468            sample(param1, «ˇparam2»);
13469        }
13470
13471        fn sample(param1: u8, param2: u8) {}
13472    "});
13473    cx.update_editor(|editor, window, cx| {
13474        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13475            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13476        })
13477    });
13478    cx.assert_editor_state(indoc! {"
13479        fn main() {
13480            sample(param1, ˇparam2);
13481        }
13482
13483        fn sample(param1: u8, param2: u8) {}
13484    "});
13485    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
13486        .await;
13487}
13488
13489#[gpui::test]
13490async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
13491    init_test(cx, |_| {});
13492
13493    let mut cx = EditorLspTestContext::new_rust(
13494        lsp::ServerCapabilities {
13495            signature_help_provider: Some(lsp::SignatureHelpOptions {
13496                ..Default::default()
13497            }),
13498            ..Default::default()
13499        },
13500        cx,
13501    )
13502    .await;
13503
13504    cx.set_state(indoc! {"
13505        fn main() {
13506            overloadedˇ
13507        }
13508    "});
13509
13510    cx.update_editor(|editor, window, cx| {
13511        editor.handle_input("(", window, cx);
13512        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13513    });
13514
13515    // Mock response with 3 signatures
13516    let mocked_response = lsp::SignatureHelp {
13517        signatures: vec![
13518            lsp::SignatureInformation {
13519                label: "fn overloaded(x: i32)".to_string(),
13520                documentation: None,
13521                parameters: Some(vec![lsp::ParameterInformation {
13522                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13523                    documentation: None,
13524                }]),
13525                active_parameter: None,
13526            },
13527            lsp::SignatureInformation {
13528                label: "fn overloaded(x: i32, y: i32)".to_string(),
13529                documentation: None,
13530                parameters: Some(vec![
13531                    lsp::ParameterInformation {
13532                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13533                        documentation: None,
13534                    },
13535                    lsp::ParameterInformation {
13536                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13537                        documentation: None,
13538                    },
13539                ]),
13540                active_parameter: None,
13541            },
13542            lsp::SignatureInformation {
13543                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
13544                documentation: None,
13545                parameters: Some(vec![
13546                    lsp::ParameterInformation {
13547                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13548                        documentation: None,
13549                    },
13550                    lsp::ParameterInformation {
13551                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13552                        documentation: None,
13553                    },
13554                    lsp::ParameterInformation {
13555                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
13556                        documentation: None,
13557                    },
13558                ]),
13559                active_parameter: None,
13560            },
13561        ],
13562        active_signature: Some(1),
13563        active_parameter: Some(0),
13564    };
13565    handle_signature_help_request(&mut cx, mocked_response).await;
13566
13567    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13568        .await;
13569
13570    // Verify we have multiple signatures and the right one is selected
13571    cx.editor(|editor, _, _| {
13572        let popover = editor.signature_help_state.popover().cloned().unwrap();
13573        assert_eq!(popover.signatures.len(), 3);
13574        // active_signature was 1, so that should be the current
13575        assert_eq!(popover.current_signature, 1);
13576        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
13577        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
13578        assert_eq!(
13579            popover.signatures[2].label,
13580            "fn overloaded(x: i32, y: i32, z: i32)"
13581        );
13582    });
13583
13584    // Test navigation functionality
13585    cx.update_editor(|editor, window, cx| {
13586        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13587    });
13588
13589    cx.editor(|editor, _, _| {
13590        let popover = editor.signature_help_state.popover().cloned().unwrap();
13591        assert_eq!(popover.current_signature, 2);
13592    });
13593
13594    // Test wrap around
13595    cx.update_editor(|editor, window, cx| {
13596        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13597    });
13598
13599    cx.editor(|editor, _, _| {
13600        let popover = editor.signature_help_state.popover().cloned().unwrap();
13601        assert_eq!(popover.current_signature, 0);
13602    });
13603
13604    // Test previous navigation
13605    cx.update_editor(|editor, window, cx| {
13606        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
13607    });
13608
13609    cx.editor(|editor, _, _| {
13610        let popover = editor.signature_help_state.popover().cloned().unwrap();
13611        assert_eq!(popover.current_signature, 2);
13612    });
13613}
13614
13615#[gpui::test]
13616async fn test_completion_mode(cx: &mut TestAppContext) {
13617    init_test(cx, |_| {});
13618    let mut cx = EditorLspTestContext::new_rust(
13619        lsp::ServerCapabilities {
13620            completion_provider: Some(lsp::CompletionOptions {
13621                resolve_provider: Some(true),
13622                ..Default::default()
13623            }),
13624            ..Default::default()
13625        },
13626        cx,
13627    )
13628    .await;
13629
13630    struct Run {
13631        run_description: &'static str,
13632        initial_state: String,
13633        buffer_marked_text: String,
13634        completion_label: &'static str,
13635        completion_text: &'static str,
13636        expected_with_insert_mode: String,
13637        expected_with_replace_mode: String,
13638        expected_with_replace_subsequence_mode: String,
13639        expected_with_replace_suffix_mode: String,
13640    }
13641
13642    let runs = [
13643        Run {
13644            run_description: "Start of word matches completion text",
13645            initial_state: "before ediˇ after".into(),
13646            buffer_marked_text: "before <edi|> after".into(),
13647            completion_label: "editor",
13648            completion_text: "editor",
13649            expected_with_insert_mode: "before editorˇ after".into(),
13650            expected_with_replace_mode: "before editorˇ after".into(),
13651            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13652            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13653        },
13654        Run {
13655            run_description: "Accept same text at the middle of the word",
13656            initial_state: "before ediˇtor after".into(),
13657            buffer_marked_text: "before <edi|tor> after".into(),
13658            completion_label: "editor",
13659            completion_text: "editor",
13660            expected_with_insert_mode: "before editorˇtor after".into(),
13661            expected_with_replace_mode: "before editorˇ after".into(),
13662            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13663            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13664        },
13665        Run {
13666            run_description: "End of word matches completion text -- cursor at end",
13667            initial_state: "before torˇ after".into(),
13668            buffer_marked_text: "before <tor|> after".into(),
13669            completion_label: "editor",
13670            completion_text: "editor",
13671            expected_with_insert_mode: "before editorˇ after".into(),
13672            expected_with_replace_mode: "before editorˇ after".into(),
13673            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13674            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13675        },
13676        Run {
13677            run_description: "End of word matches completion text -- cursor at start",
13678            initial_state: "before ˇtor after".into(),
13679            buffer_marked_text: "before <|tor> after".into(),
13680            completion_label: "editor",
13681            completion_text: "editor",
13682            expected_with_insert_mode: "before editorˇtor after".into(),
13683            expected_with_replace_mode: "before editorˇ after".into(),
13684            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13685            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13686        },
13687        Run {
13688            run_description: "Prepend text containing whitespace",
13689            initial_state: "pˇfield: bool".into(),
13690            buffer_marked_text: "<p|field>: bool".into(),
13691            completion_label: "pub ",
13692            completion_text: "pub ",
13693            expected_with_insert_mode: "pub ˇfield: bool".into(),
13694            expected_with_replace_mode: "pub ˇ: bool".into(),
13695            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
13696            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
13697        },
13698        Run {
13699            run_description: "Add element to start of list",
13700            initial_state: "[element_ˇelement_2]".into(),
13701            buffer_marked_text: "[<element_|element_2>]".into(),
13702            completion_label: "element_1",
13703            completion_text: "element_1",
13704            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
13705            expected_with_replace_mode: "[element_1ˇ]".into(),
13706            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
13707            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
13708        },
13709        Run {
13710            run_description: "Add element to start of list -- first and second elements are equal",
13711            initial_state: "[elˇelement]".into(),
13712            buffer_marked_text: "[<el|element>]".into(),
13713            completion_label: "element",
13714            completion_text: "element",
13715            expected_with_insert_mode: "[elementˇelement]".into(),
13716            expected_with_replace_mode: "[elementˇ]".into(),
13717            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
13718            expected_with_replace_suffix_mode: "[elementˇ]".into(),
13719        },
13720        Run {
13721            run_description: "Ends with matching suffix",
13722            initial_state: "SubˇError".into(),
13723            buffer_marked_text: "<Sub|Error>".into(),
13724            completion_label: "SubscriptionError",
13725            completion_text: "SubscriptionError",
13726            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
13727            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13728            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13729            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
13730        },
13731        Run {
13732            run_description: "Suffix is a subsequence -- contiguous",
13733            initial_state: "SubˇErr".into(),
13734            buffer_marked_text: "<Sub|Err>".into(),
13735            completion_label: "SubscriptionError",
13736            completion_text: "SubscriptionError",
13737            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
13738            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13739            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13740            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
13741        },
13742        Run {
13743            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
13744            initial_state: "Suˇscrirr".into(),
13745            buffer_marked_text: "<Su|scrirr>".into(),
13746            completion_label: "SubscriptionError",
13747            completion_text: "SubscriptionError",
13748            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
13749            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13750            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13751            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
13752        },
13753        Run {
13754            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
13755            initial_state: "foo(indˇix)".into(),
13756            buffer_marked_text: "foo(<ind|ix>)".into(),
13757            completion_label: "node_index",
13758            completion_text: "node_index",
13759            expected_with_insert_mode: "foo(node_indexˇix)".into(),
13760            expected_with_replace_mode: "foo(node_indexˇ)".into(),
13761            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
13762            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
13763        },
13764        Run {
13765            run_description: "Replace range ends before cursor - should extend to cursor",
13766            initial_state: "before editˇo after".into(),
13767            buffer_marked_text: "before <{ed}>it|o after".into(),
13768            completion_label: "editor",
13769            completion_text: "editor",
13770            expected_with_insert_mode: "before editorˇo after".into(),
13771            expected_with_replace_mode: "before editorˇo after".into(),
13772            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
13773            expected_with_replace_suffix_mode: "before editorˇo after".into(),
13774        },
13775        Run {
13776            run_description: "Uses label for suffix matching",
13777            initial_state: "before ediˇtor after".into(),
13778            buffer_marked_text: "before <edi|tor> after".into(),
13779            completion_label: "editor",
13780            completion_text: "editor()",
13781            expected_with_insert_mode: "before editor()ˇtor after".into(),
13782            expected_with_replace_mode: "before editor()ˇ after".into(),
13783            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
13784            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
13785        },
13786        Run {
13787            run_description: "Case insensitive subsequence and suffix matching",
13788            initial_state: "before EDiˇtoR after".into(),
13789            buffer_marked_text: "before <EDi|toR> after".into(),
13790            completion_label: "editor",
13791            completion_text: "editor",
13792            expected_with_insert_mode: "before editorˇtoR after".into(),
13793            expected_with_replace_mode: "before editorˇ after".into(),
13794            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13795            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13796        },
13797    ];
13798
13799    for run in runs {
13800        let run_variations = [
13801            (LspInsertMode::Insert, run.expected_with_insert_mode),
13802            (LspInsertMode::Replace, run.expected_with_replace_mode),
13803            (
13804                LspInsertMode::ReplaceSubsequence,
13805                run.expected_with_replace_subsequence_mode,
13806            ),
13807            (
13808                LspInsertMode::ReplaceSuffix,
13809                run.expected_with_replace_suffix_mode,
13810            ),
13811        ];
13812
13813        for (lsp_insert_mode, expected_text) in run_variations {
13814            eprintln!(
13815                "run = {:?}, mode = {lsp_insert_mode:.?}",
13816                run.run_description,
13817            );
13818
13819            update_test_language_settings(&mut cx, |settings| {
13820                settings.defaults.completions = Some(CompletionSettingsContent {
13821                    lsp_insert_mode: Some(lsp_insert_mode),
13822                    words: Some(WordsCompletionMode::Disabled),
13823                    words_min_length: Some(0),
13824                    ..Default::default()
13825                });
13826            });
13827
13828            cx.set_state(&run.initial_state);
13829            cx.update_editor(|editor, window, cx| {
13830                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13831            });
13832
13833            let counter = Arc::new(AtomicUsize::new(0));
13834            handle_completion_request_with_insert_and_replace(
13835                &mut cx,
13836                &run.buffer_marked_text,
13837                vec![(run.completion_label, run.completion_text)],
13838                counter.clone(),
13839            )
13840            .await;
13841            cx.condition(|editor, _| editor.context_menu_visible())
13842                .await;
13843            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13844
13845            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13846                editor
13847                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
13848                    .unwrap()
13849            });
13850            cx.assert_editor_state(&expected_text);
13851            handle_resolve_completion_request(&mut cx, None).await;
13852            apply_additional_edits.await.unwrap();
13853        }
13854    }
13855}
13856
13857#[gpui::test]
13858async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
13859    init_test(cx, |_| {});
13860    let mut cx = EditorLspTestContext::new_rust(
13861        lsp::ServerCapabilities {
13862            completion_provider: Some(lsp::CompletionOptions {
13863                resolve_provider: Some(true),
13864                ..Default::default()
13865            }),
13866            ..Default::default()
13867        },
13868        cx,
13869    )
13870    .await;
13871
13872    let initial_state = "SubˇError";
13873    let buffer_marked_text = "<Sub|Error>";
13874    let completion_text = "SubscriptionError";
13875    let expected_with_insert_mode = "SubscriptionErrorˇError";
13876    let expected_with_replace_mode = "SubscriptionErrorˇ";
13877
13878    update_test_language_settings(&mut cx, |settings| {
13879        settings.defaults.completions = Some(CompletionSettingsContent {
13880            words: Some(WordsCompletionMode::Disabled),
13881            words_min_length: Some(0),
13882            // set the opposite here to ensure that the action is overriding the default behavior
13883            lsp_insert_mode: Some(LspInsertMode::Insert),
13884            ..Default::default()
13885        });
13886    });
13887
13888    cx.set_state(initial_state);
13889    cx.update_editor(|editor, window, cx| {
13890        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13891    });
13892
13893    let counter = Arc::new(AtomicUsize::new(0));
13894    handle_completion_request_with_insert_and_replace(
13895        &mut cx,
13896        buffer_marked_text,
13897        vec![(completion_text, completion_text)],
13898        counter.clone(),
13899    )
13900    .await;
13901    cx.condition(|editor, _| editor.context_menu_visible())
13902        .await;
13903    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13904
13905    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13906        editor
13907            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13908            .unwrap()
13909    });
13910    cx.assert_editor_state(expected_with_replace_mode);
13911    handle_resolve_completion_request(&mut cx, None).await;
13912    apply_additional_edits.await.unwrap();
13913
13914    update_test_language_settings(&mut cx, |settings| {
13915        settings.defaults.completions = Some(CompletionSettingsContent {
13916            words: Some(WordsCompletionMode::Disabled),
13917            words_min_length: Some(0),
13918            // set the opposite here to ensure that the action is overriding the default behavior
13919            lsp_insert_mode: Some(LspInsertMode::Replace),
13920            ..Default::default()
13921        });
13922    });
13923
13924    cx.set_state(initial_state);
13925    cx.update_editor(|editor, window, cx| {
13926        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13927    });
13928    handle_completion_request_with_insert_and_replace(
13929        &mut cx,
13930        buffer_marked_text,
13931        vec![(completion_text, completion_text)],
13932        counter.clone(),
13933    )
13934    .await;
13935    cx.condition(|editor, _| editor.context_menu_visible())
13936        .await;
13937    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13938
13939    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13940        editor
13941            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
13942            .unwrap()
13943    });
13944    cx.assert_editor_state(expected_with_insert_mode);
13945    handle_resolve_completion_request(&mut cx, None).await;
13946    apply_additional_edits.await.unwrap();
13947}
13948
13949#[gpui::test]
13950async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
13951    init_test(cx, |_| {});
13952    let mut cx = EditorLspTestContext::new_rust(
13953        lsp::ServerCapabilities {
13954            completion_provider: Some(lsp::CompletionOptions {
13955                resolve_provider: Some(true),
13956                ..Default::default()
13957            }),
13958            ..Default::default()
13959        },
13960        cx,
13961    )
13962    .await;
13963
13964    // scenario: surrounding text matches completion text
13965    let completion_text = "to_offset";
13966    let initial_state = indoc! {"
13967        1. buf.to_offˇsuffix
13968        2. buf.to_offˇsuf
13969        3. buf.to_offˇfix
13970        4. buf.to_offˇ
13971        5. into_offˇensive
13972        6. ˇsuffix
13973        7. let ˇ //
13974        8. aaˇzz
13975        9. buf.to_off«zzzzzˇ»suffix
13976        10. buf.«ˇzzzzz»suffix
13977        11. to_off«ˇzzzzz»
13978
13979        buf.to_offˇsuffix  // newest cursor
13980    "};
13981    let completion_marked_buffer = indoc! {"
13982        1. buf.to_offsuffix
13983        2. buf.to_offsuf
13984        3. buf.to_offfix
13985        4. buf.to_off
13986        5. into_offensive
13987        6. suffix
13988        7. let  //
13989        8. aazz
13990        9. buf.to_offzzzzzsuffix
13991        10. buf.zzzzzsuffix
13992        11. to_offzzzzz
13993
13994        buf.<to_off|suffix>  // newest cursor
13995    "};
13996    let expected = indoc! {"
13997        1. buf.to_offsetˇ
13998        2. buf.to_offsetˇsuf
13999        3. buf.to_offsetˇfix
14000        4. buf.to_offsetˇ
14001        5. into_offsetˇensive
14002        6. to_offsetˇsuffix
14003        7. let to_offsetˇ //
14004        8. aato_offsetˇzz
14005        9. buf.to_offsetˇ
14006        10. buf.to_offsetˇsuffix
14007        11. to_offsetˇ
14008
14009        buf.to_offsetˇ  // newest cursor
14010    "};
14011    cx.set_state(initial_state);
14012    cx.update_editor(|editor, window, cx| {
14013        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14014    });
14015    handle_completion_request_with_insert_and_replace(
14016        &mut cx,
14017        completion_marked_buffer,
14018        vec![(completion_text, completion_text)],
14019        Arc::new(AtomicUsize::new(0)),
14020    )
14021    .await;
14022    cx.condition(|editor, _| editor.context_menu_visible())
14023        .await;
14024    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14025        editor
14026            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14027            .unwrap()
14028    });
14029    cx.assert_editor_state(expected);
14030    handle_resolve_completion_request(&mut cx, None).await;
14031    apply_additional_edits.await.unwrap();
14032
14033    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
14034    let completion_text = "foo_and_bar";
14035    let initial_state = indoc! {"
14036        1. ooanbˇ
14037        2. zooanbˇ
14038        3. ooanbˇz
14039        4. zooanbˇz
14040        5. ooanˇ
14041        6. oanbˇ
14042
14043        ooanbˇ
14044    "};
14045    let completion_marked_buffer = indoc! {"
14046        1. ooanb
14047        2. zooanb
14048        3. ooanbz
14049        4. zooanbz
14050        5. ooan
14051        6. oanb
14052
14053        <ooanb|>
14054    "};
14055    let expected = indoc! {"
14056        1. foo_and_barˇ
14057        2. zfoo_and_barˇ
14058        3. foo_and_barˇz
14059        4. zfoo_and_barˇz
14060        5. ooanfoo_and_barˇ
14061        6. oanbfoo_and_barˇ
14062
14063        foo_and_barˇ
14064    "};
14065    cx.set_state(initial_state);
14066    cx.update_editor(|editor, window, cx| {
14067        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14068    });
14069    handle_completion_request_with_insert_and_replace(
14070        &mut cx,
14071        completion_marked_buffer,
14072        vec![(completion_text, completion_text)],
14073        Arc::new(AtomicUsize::new(0)),
14074    )
14075    .await;
14076    cx.condition(|editor, _| editor.context_menu_visible())
14077        .await;
14078    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14079        editor
14080            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14081            .unwrap()
14082    });
14083    cx.assert_editor_state(expected);
14084    handle_resolve_completion_request(&mut cx, None).await;
14085    apply_additional_edits.await.unwrap();
14086
14087    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
14088    // (expects the same as if it was inserted at the end)
14089    let completion_text = "foo_and_bar";
14090    let initial_state = indoc! {"
14091        1. ooˇanb
14092        2. zooˇanb
14093        3. ooˇanbz
14094        4. zooˇanbz
14095
14096        ooˇanb
14097    "};
14098    let completion_marked_buffer = indoc! {"
14099        1. ooanb
14100        2. zooanb
14101        3. ooanbz
14102        4. zooanbz
14103
14104        <oo|anb>
14105    "};
14106    let expected = indoc! {"
14107        1. foo_and_barˇ
14108        2. zfoo_and_barˇ
14109        3. foo_and_barˇz
14110        4. zfoo_and_barˇz
14111
14112        foo_and_barˇ
14113    "};
14114    cx.set_state(initial_state);
14115    cx.update_editor(|editor, window, cx| {
14116        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14117    });
14118    handle_completion_request_with_insert_and_replace(
14119        &mut cx,
14120        completion_marked_buffer,
14121        vec![(completion_text, completion_text)],
14122        Arc::new(AtomicUsize::new(0)),
14123    )
14124    .await;
14125    cx.condition(|editor, _| editor.context_menu_visible())
14126        .await;
14127    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14128        editor
14129            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14130            .unwrap()
14131    });
14132    cx.assert_editor_state(expected);
14133    handle_resolve_completion_request(&mut cx, None).await;
14134    apply_additional_edits.await.unwrap();
14135}
14136
14137// This used to crash
14138#[gpui::test]
14139async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
14140    init_test(cx, |_| {});
14141
14142    let buffer_text = indoc! {"
14143        fn main() {
14144            10.satu;
14145
14146            //
14147            // separate cursors so they open in different excerpts (manually reproducible)
14148            //
14149
14150            10.satu20;
14151        }
14152    "};
14153    let multibuffer_text_with_selections = indoc! {"
14154        fn main() {
14155            10.satuˇ;
14156
14157            //
14158
14159            //
14160
14161            10.satuˇ20;
14162        }
14163    "};
14164    let expected_multibuffer = indoc! {"
14165        fn main() {
14166            10.saturating_sub()ˇ;
14167
14168            //
14169
14170            //
14171
14172            10.saturating_sub()ˇ;
14173        }
14174    "};
14175
14176    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
14177    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
14178
14179    let fs = FakeFs::new(cx.executor());
14180    fs.insert_tree(
14181        path!("/a"),
14182        json!({
14183            "main.rs": buffer_text,
14184        }),
14185    )
14186    .await;
14187
14188    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14189    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14190    language_registry.add(rust_lang());
14191    let mut fake_servers = language_registry.register_fake_lsp(
14192        "Rust",
14193        FakeLspAdapter {
14194            capabilities: lsp::ServerCapabilities {
14195                completion_provider: Some(lsp::CompletionOptions {
14196                    resolve_provider: None,
14197                    ..lsp::CompletionOptions::default()
14198                }),
14199                ..lsp::ServerCapabilities::default()
14200            },
14201            ..FakeLspAdapter::default()
14202        },
14203    );
14204    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14205    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14206    let buffer = project
14207        .update(cx, |project, cx| {
14208            project.open_local_buffer(path!("/a/main.rs"), cx)
14209        })
14210        .await
14211        .unwrap();
14212
14213    let multi_buffer = cx.new(|cx| {
14214        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
14215        multi_buffer.push_excerpts(
14216            buffer.clone(),
14217            [ExcerptRange::new(0..first_excerpt_end)],
14218            cx,
14219        );
14220        multi_buffer.push_excerpts(
14221            buffer.clone(),
14222            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
14223            cx,
14224        );
14225        multi_buffer
14226    });
14227
14228    let editor = workspace
14229        .update(cx, |_, window, cx| {
14230            cx.new(|cx| {
14231                Editor::new(
14232                    EditorMode::Full {
14233                        scale_ui_elements_with_buffer_font_size: false,
14234                        show_active_line_background: false,
14235                        sizing_behavior: SizingBehavior::Default,
14236                    },
14237                    multi_buffer.clone(),
14238                    Some(project.clone()),
14239                    window,
14240                    cx,
14241                )
14242            })
14243        })
14244        .unwrap();
14245
14246    let pane = workspace
14247        .update(cx, |workspace, _, _| workspace.active_pane().clone())
14248        .unwrap();
14249    pane.update_in(cx, |pane, window, cx| {
14250        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
14251    });
14252
14253    let fake_server = fake_servers.next().await.unwrap();
14254
14255    editor.update_in(cx, |editor, window, cx| {
14256        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14257            s.select_ranges([
14258                Point::new(1, 11)..Point::new(1, 11),
14259                Point::new(7, 11)..Point::new(7, 11),
14260            ])
14261        });
14262
14263        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
14264    });
14265
14266    editor.update_in(cx, |editor, window, cx| {
14267        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14268    });
14269
14270    fake_server
14271        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14272            let completion_item = lsp::CompletionItem {
14273                label: "saturating_sub()".into(),
14274                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
14275                    lsp::InsertReplaceEdit {
14276                        new_text: "saturating_sub()".to_owned(),
14277                        insert: lsp::Range::new(
14278                            lsp::Position::new(7, 7),
14279                            lsp::Position::new(7, 11),
14280                        ),
14281                        replace: lsp::Range::new(
14282                            lsp::Position::new(7, 7),
14283                            lsp::Position::new(7, 13),
14284                        ),
14285                    },
14286                )),
14287                ..lsp::CompletionItem::default()
14288            };
14289
14290            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
14291        })
14292        .next()
14293        .await
14294        .unwrap();
14295
14296    cx.condition(&editor, |editor, _| editor.context_menu_visible())
14297        .await;
14298
14299    editor
14300        .update_in(cx, |editor, window, cx| {
14301            editor
14302                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14303                .unwrap()
14304        })
14305        .await
14306        .unwrap();
14307
14308    editor.update(cx, |editor, cx| {
14309        assert_text_with_selections(editor, expected_multibuffer, cx);
14310    })
14311}
14312
14313#[gpui::test]
14314async fn test_completion(cx: &mut TestAppContext) {
14315    init_test(cx, |_| {});
14316
14317    let mut cx = EditorLspTestContext::new_rust(
14318        lsp::ServerCapabilities {
14319            completion_provider: Some(lsp::CompletionOptions {
14320                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14321                resolve_provider: Some(true),
14322                ..Default::default()
14323            }),
14324            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14325            ..Default::default()
14326        },
14327        cx,
14328    )
14329    .await;
14330    let counter = Arc::new(AtomicUsize::new(0));
14331
14332    cx.set_state(indoc! {"
14333        oneˇ
14334        two
14335        three
14336    "});
14337    cx.simulate_keystroke(".");
14338    handle_completion_request(
14339        indoc! {"
14340            one.|<>
14341            two
14342            three
14343        "},
14344        vec!["first_completion", "second_completion"],
14345        true,
14346        counter.clone(),
14347        &mut cx,
14348    )
14349    .await;
14350    cx.condition(|editor, _| editor.context_menu_visible())
14351        .await;
14352    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14353
14354    let _handler = handle_signature_help_request(
14355        &mut cx,
14356        lsp::SignatureHelp {
14357            signatures: vec![lsp::SignatureInformation {
14358                label: "test signature".to_string(),
14359                documentation: None,
14360                parameters: Some(vec![lsp::ParameterInformation {
14361                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
14362                    documentation: None,
14363                }]),
14364                active_parameter: None,
14365            }],
14366            active_signature: None,
14367            active_parameter: None,
14368        },
14369    );
14370    cx.update_editor(|editor, window, cx| {
14371        assert!(
14372            !editor.signature_help_state.is_shown(),
14373            "No signature help was called for"
14374        );
14375        editor.show_signature_help(&ShowSignatureHelp, window, cx);
14376    });
14377    cx.run_until_parked();
14378    cx.update_editor(|editor, _, _| {
14379        assert!(
14380            !editor.signature_help_state.is_shown(),
14381            "No signature help should be shown when completions menu is open"
14382        );
14383    });
14384
14385    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14386        editor.context_menu_next(&Default::default(), window, cx);
14387        editor
14388            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14389            .unwrap()
14390    });
14391    cx.assert_editor_state(indoc! {"
14392        one.second_completionˇ
14393        two
14394        three
14395    "});
14396
14397    handle_resolve_completion_request(
14398        &mut cx,
14399        Some(vec![
14400            (
14401                //This overlaps with the primary completion edit which is
14402                //misbehavior from the LSP spec, test that we filter it out
14403                indoc! {"
14404                    one.second_ˇcompletion
14405                    two
14406                    threeˇ
14407                "},
14408                "overlapping additional edit",
14409            ),
14410            (
14411                indoc! {"
14412                    one.second_completion
14413                    two
14414                    threeˇ
14415                "},
14416                "\nadditional edit",
14417            ),
14418        ]),
14419    )
14420    .await;
14421    apply_additional_edits.await.unwrap();
14422    cx.assert_editor_state(indoc! {"
14423        one.second_completionˇ
14424        two
14425        three
14426        additional edit
14427    "});
14428
14429    cx.set_state(indoc! {"
14430        one.second_completion
14431        twoˇ
14432        threeˇ
14433        additional edit
14434    "});
14435    cx.simulate_keystroke(" ");
14436    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14437    cx.simulate_keystroke("s");
14438    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14439
14440    cx.assert_editor_state(indoc! {"
14441        one.second_completion
14442        two sˇ
14443        three sˇ
14444        additional edit
14445    "});
14446    handle_completion_request(
14447        indoc! {"
14448            one.second_completion
14449            two s
14450            three <s|>
14451            additional edit
14452        "},
14453        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14454        true,
14455        counter.clone(),
14456        &mut cx,
14457    )
14458    .await;
14459    cx.condition(|editor, _| editor.context_menu_visible())
14460        .await;
14461    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14462
14463    cx.simulate_keystroke("i");
14464
14465    handle_completion_request(
14466        indoc! {"
14467            one.second_completion
14468            two si
14469            three <si|>
14470            additional edit
14471        "},
14472        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14473        true,
14474        counter.clone(),
14475        &mut cx,
14476    )
14477    .await;
14478    cx.condition(|editor, _| editor.context_menu_visible())
14479        .await;
14480    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14481
14482    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14483        editor
14484            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14485            .unwrap()
14486    });
14487    cx.assert_editor_state(indoc! {"
14488        one.second_completion
14489        two sixth_completionˇ
14490        three sixth_completionˇ
14491        additional edit
14492    "});
14493
14494    apply_additional_edits.await.unwrap();
14495
14496    update_test_language_settings(&mut cx, |settings| {
14497        settings.defaults.show_completions_on_input = Some(false);
14498    });
14499    cx.set_state("editorˇ");
14500    cx.simulate_keystroke(".");
14501    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14502    cx.simulate_keystrokes("c l o");
14503    cx.assert_editor_state("editor.cloˇ");
14504    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14505    cx.update_editor(|editor, window, cx| {
14506        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14507    });
14508    handle_completion_request(
14509        "editor.<clo|>",
14510        vec!["close", "clobber"],
14511        true,
14512        counter.clone(),
14513        &mut cx,
14514    )
14515    .await;
14516    cx.condition(|editor, _| editor.context_menu_visible())
14517        .await;
14518    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
14519
14520    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14521        editor
14522            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14523            .unwrap()
14524    });
14525    cx.assert_editor_state("editor.clobberˇ");
14526    handle_resolve_completion_request(&mut cx, None).await;
14527    apply_additional_edits.await.unwrap();
14528}
14529
14530#[gpui::test]
14531async fn test_completion_reuse(cx: &mut TestAppContext) {
14532    init_test(cx, |_| {});
14533
14534    let mut cx = EditorLspTestContext::new_rust(
14535        lsp::ServerCapabilities {
14536            completion_provider: Some(lsp::CompletionOptions {
14537                trigger_characters: Some(vec![".".to_string()]),
14538                ..Default::default()
14539            }),
14540            ..Default::default()
14541        },
14542        cx,
14543    )
14544    .await;
14545
14546    let counter = Arc::new(AtomicUsize::new(0));
14547    cx.set_state("objˇ");
14548    cx.simulate_keystroke(".");
14549
14550    // Initial completion request returns complete results
14551    let is_incomplete = false;
14552    handle_completion_request(
14553        "obj.|<>",
14554        vec!["a", "ab", "abc"],
14555        is_incomplete,
14556        counter.clone(),
14557        &mut cx,
14558    )
14559    .await;
14560    cx.run_until_parked();
14561    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14562    cx.assert_editor_state("obj.ˇ");
14563    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14564
14565    // Type "a" - filters existing completions
14566    cx.simulate_keystroke("a");
14567    cx.run_until_parked();
14568    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14569    cx.assert_editor_state("obj.aˇ");
14570    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14571
14572    // Type "b" - filters existing completions
14573    cx.simulate_keystroke("b");
14574    cx.run_until_parked();
14575    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14576    cx.assert_editor_state("obj.abˇ");
14577    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14578
14579    // Type "c" - filters existing completions
14580    cx.simulate_keystroke("c");
14581    cx.run_until_parked();
14582    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14583    cx.assert_editor_state("obj.abcˇ");
14584    check_displayed_completions(vec!["abc"], &mut cx);
14585
14586    // Backspace to delete "c" - filters existing completions
14587    cx.update_editor(|editor, window, cx| {
14588        editor.backspace(&Backspace, window, cx);
14589    });
14590    cx.run_until_parked();
14591    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14592    cx.assert_editor_state("obj.abˇ");
14593    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14594
14595    // Moving cursor to the left dismisses menu.
14596    cx.update_editor(|editor, window, cx| {
14597        editor.move_left(&MoveLeft, window, cx);
14598    });
14599    cx.run_until_parked();
14600    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14601    cx.assert_editor_state("obj.aˇb");
14602    cx.update_editor(|editor, _, _| {
14603        assert_eq!(editor.context_menu_visible(), false);
14604    });
14605
14606    // Type "b" - new request
14607    cx.simulate_keystroke("b");
14608    let is_incomplete = false;
14609    handle_completion_request(
14610        "obj.<ab|>a",
14611        vec!["ab", "abc"],
14612        is_incomplete,
14613        counter.clone(),
14614        &mut cx,
14615    )
14616    .await;
14617    cx.run_until_parked();
14618    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14619    cx.assert_editor_state("obj.abˇb");
14620    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14621
14622    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
14623    cx.update_editor(|editor, window, cx| {
14624        editor.backspace(&Backspace, window, cx);
14625    });
14626    let is_incomplete = false;
14627    handle_completion_request(
14628        "obj.<a|>b",
14629        vec!["a", "ab", "abc"],
14630        is_incomplete,
14631        counter.clone(),
14632        &mut cx,
14633    )
14634    .await;
14635    cx.run_until_parked();
14636    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14637    cx.assert_editor_state("obj.aˇb");
14638    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14639
14640    // Backspace to delete "a" - dismisses menu.
14641    cx.update_editor(|editor, window, cx| {
14642        editor.backspace(&Backspace, window, cx);
14643    });
14644    cx.run_until_parked();
14645    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14646    cx.assert_editor_state("obj.ˇb");
14647    cx.update_editor(|editor, _, _| {
14648        assert_eq!(editor.context_menu_visible(), false);
14649    });
14650}
14651
14652#[gpui::test]
14653async fn test_word_completion(cx: &mut TestAppContext) {
14654    let lsp_fetch_timeout_ms = 10;
14655    init_test(cx, |language_settings| {
14656        language_settings.defaults.completions = Some(CompletionSettingsContent {
14657            words_min_length: Some(0),
14658            lsp_fetch_timeout_ms: Some(10),
14659            lsp_insert_mode: Some(LspInsertMode::Insert),
14660            ..Default::default()
14661        });
14662    });
14663
14664    let mut cx = EditorLspTestContext::new_rust(
14665        lsp::ServerCapabilities {
14666            completion_provider: Some(lsp::CompletionOptions {
14667                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14668                ..lsp::CompletionOptions::default()
14669            }),
14670            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14671            ..lsp::ServerCapabilities::default()
14672        },
14673        cx,
14674    )
14675    .await;
14676
14677    let throttle_completions = Arc::new(AtomicBool::new(false));
14678
14679    let lsp_throttle_completions = throttle_completions.clone();
14680    let _completion_requests_handler =
14681        cx.lsp
14682            .server
14683            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
14684                let lsp_throttle_completions = lsp_throttle_completions.clone();
14685                let cx = cx.clone();
14686                async move {
14687                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
14688                        cx.background_executor()
14689                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
14690                            .await;
14691                    }
14692                    Ok(Some(lsp::CompletionResponse::Array(vec![
14693                        lsp::CompletionItem {
14694                            label: "first".into(),
14695                            ..lsp::CompletionItem::default()
14696                        },
14697                        lsp::CompletionItem {
14698                            label: "last".into(),
14699                            ..lsp::CompletionItem::default()
14700                        },
14701                    ])))
14702                }
14703            });
14704
14705    cx.set_state(indoc! {"
14706        oneˇ
14707        two
14708        three
14709    "});
14710    cx.simulate_keystroke(".");
14711    cx.executor().run_until_parked();
14712    cx.condition(|editor, _| editor.context_menu_visible())
14713        .await;
14714    cx.update_editor(|editor, window, cx| {
14715        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14716        {
14717            assert_eq!(
14718                completion_menu_entries(menu),
14719                &["first", "last"],
14720                "When LSP server is fast to reply, no fallback word completions are used"
14721            );
14722        } else {
14723            panic!("expected completion menu to be open");
14724        }
14725        editor.cancel(&Cancel, window, cx);
14726    });
14727    cx.executor().run_until_parked();
14728    cx.condition(|editor, _| !editor.context_menu_visible())
14729        .await;
14730
14731    throttle_completions.store(true, atomic::Ordering::Release);
14732    cx.simulate_keystroke(".");
14733    cx.executor()
14734        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
14735    cx.executor().run_until_parked();
14736    cx.condition(|editor, _| editor.context_menu_visible())
14737        .await;
14738    cx.update_editor(|editor, _, _| {
14739        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14740        {
14741            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
14742                "When LSP server is slow, document words can be shown instead, if configured accordingly");
14743        } else {
14744            panic!("expected completion menu to be open");
14745        }
14746    });
14747}
14748
14749#[gpui::test]
14750async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
14751    init_test(cx, |language_settings| {
14752        language_settings.defaults.completions = Some(CompletionSettingsContent {
14753            words: Some(WordsCompletionMode::Enabled),
14754            words_min_length: Some(0),
14755            lsp_insert_mode: Some(LspInsertMode::Insert),
14756            ..Default::default()
14757        });
14758    });
14759
14760    let mut cx = EditorLspTestContext::new_rust(
14761        lsp::ServerCapabilities {
14762            completion_provider: Some(lsp::CompletionOptions {
14763                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14764                ..lsp::CompletionOptions::default()
14765            }),
14766            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14767            ..lsp::ServerCapabilities::default()
14768        },
14769        cx,
14770    )
14771    .await;
14772
14773    let _completion_requests_handler =
14774        cx.lsp
14775            .server
14776            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14777                Ok(Some(lsp::CompletionResponse::Array(vec![
14778                    lsp::CompletionItem {
14779                        label: "first".into(),
14780                        ..lsp::CompletionItem::default()
14781                    },
14782                    lsp::CompletionItem {
14783                        label: "last".into(),
14784                        ..lsp::CompletionItem::default()
14785                    },
14786                ])))
14787            });
14788
14789    cx.set_state(indoc! {"ˇ
14790        first
14791        last
14792        second
14793    "});
14794    cx.simulate_keystroke(".");
14795    cx.executor().run_until_parked();
14796    cx.condition(|editor, _| editor.context_menu_visible())
14797        .await;
14798    cx.update_editor(|editor, _, _| {
14799        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14800        {
14801            assert_eq!(
14802                completion_menu_entries(menu),
14803                &["first", "last", "second"],
14804                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
14805            );
14806        } else {
14807            panic!("expected completion menu to be open");
14808        }
14809    });
14810}
14811
14812#[gpui::test]
14813async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
14814    init_test(cx, |language_settings| {
14815        language_settings.defaults.completions = Some(CompletionSettingsContent {
14816            words: Some(WordsCompletionMode::Disabled),
14817            words_min_length: Some(0),
14818            lsp_insert_mode: Some(LspInsertMode::Insert),
14819            ..Default::default()
14820        });
14821    });
14822
14823    let mut cx = EditorLspTestContext::new_rust(
14824        lsp::ServerCapabilities {
14825            completion_provider: Some(lsp::CompletionOptions {
14826                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14827                ..lsp::CompletionOptions::default()
14828            }),
14829            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14830            ..lsp::ServerCapabilities::default()
14831        },
14832        cx,
14833    )
14834    .await;
14835
14836    let _completion_requests_handler =
14837        cx.lsp
14838            .server
14839            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14840                panic!("LSP completions should not be queried when dealing with word completions")
14841            });
14842
14843    cx.set_state(indoc! {"ˇ
14844        first
14845        last
14846        second
14847    "});
14848    cx.update_editor(|editor, window, cx| {
14849        editor.show_word_completions(&ShowWordCompletions, window, cx);
14850    });
14851    cx.executor().run_until_parked();
14852    cx.condition(|editor, _| editor.context_menu_visible())
14853        .await;
14854    cx.update_editor(|editor, _, _| {
14855        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14856        {
14857            assert_eq!(
14858                completion_menu_entries(menu),
14859                &["first", "last", "second"],
14860                "`ShowWordCompletions` action should show word completions"
14861            );
14862        } else {
14863            panic!("expected completion menu to be open");
14864        }
14865    });
14866
14867    cx.simulate_keystroke("l");
14868    cx.executor().run_until_parked();
14869    cx.condition(|editor, _| editor.context_menu_visible())
14870        .await;
14871    cx.update_editor(|editor, _, _| {
14872        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14873        {
14874            assert_eq!(
14875                completion_menu_entries(menu),
14876                &["last"],
14877                "After showing word completions, further editing should filter them and not query the LSP"
14878            );
14879        } else {
14880            panic!("expected completion menu to be open");
14881        }
14882    });
14883}
14884
14885#[gpui::test]
14886async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
14887    init_test(cx, |language_settings| {
14888        language_settings.defaults.completions = Some(CompletionSettingsContent {
14889            words_min_length: Some(0),
14890            lsp: Some(false),
14891            lsp_insert_mode: Some(LspInsertMode::Insert),
14892            ..Default::default()
14893        });
14894    });
14895
14896    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14897
14898    cx.set_state(indoc! {"ˇ
14899        0_usize
14900        let
14901        33
14902        4.5f32
14903    "});
14904    cx.update_editor(|editor, window, cx| {
14905        editor.show_completions(&ShowCompletions::default(), window, cx);
14906    });
14907    cx.executor().run_until_parked();
14908    cx.condition(|editor, _| editor.context_menu_visible())
14909        .await;
14910    cx.update_editor(|editor, window, cx| {
14911        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14912        {
14913            assert_eq!(
14914                completion_menu_entries(menu),
14915                &["let"],
14916                "With no digits in the completion query, no digits should be in the word completions"
14917            );
14918        } else {
14919            panic!("expected completion menu to be open");
14920        }
14921        editor.cancel(&Cancel, window, cx);
14922    });
14923
14924    cx.set_state(indoc! {"14925        0_usize
14926        let
14927        3
14928        33.35f32
14929    "});
14930    cx.update_editor(|editor, window, cx| {
14931        editor.show_completions(&ShowCompletions::default(), window, cx);
14932    });
14933    cx.executor().run_until_parked();
14934    cx.condition(|editor, _| editor.context_menu_visible())
14935        .await;
14936    cx.update_editor(|editor, _, _| {
14937        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14938        {
14939            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
14940                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
14941        } else {
14942            panic!("expected completion menu to be open");
14943        }
14944    });
14945}
14946
14947#[gpui::test]
14948async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
14949    init_test(cx, |language_settings| {
14950        language_settings.defaults.completions = Some(CompletionSettingsContent {
14951            words: Some(WordsCompletionMode::Enabled),
14952            words_min_length: Some(3),
14953            lsp_insert_mode: Some(LspInsertMode::Insert),
14954            ..Default::default()
14955        });
14956    });
14957
14958    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14959    cx.set_state(indoc! {"ˇ
14960        wow
14961        wowen
14962        wowser
14963    "});
14964    cx.simulate_keystroke("w");
14965    cx.executor().run_until_parked();
14966    cx.update_editor(|editor, _, _| {
14967        if editor.context_menu.borrow_mut().is_some() {
14968            panic!(
14969                "expected completion menu to be hidden, as words completion threshold is not met"
14970            );
14971        }
14972    });
14973
14974    cx.update_editor(|editor, window, cx| {
14975        editor.show_word_completions(&ShowWordCompletions, window, cx);
14976    });
14977    cx.executor().run_until_parked();
14978    cx.update_editor(|editor, window, cx| {
14979        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14980        {
14981            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");
14982        } else {
14983            panic!("expected completion menu to be open after the word completions are called with an action");
14984        }
14985
14986        editor.cancel(&Cancel, window, cx);
14987    });
14988    cx.update_editor(|editor, _, _| {
14989        if editor.context_menu.borrow_mut().is_some() {
14990            panic!("expected completion menu to be hidden after canceling");
14991        }
14992    });
14993
14994    cx.simulate_keystroke("o");
14995    cx.executor().run_until_parked();
14996    cx.update_editor(|editor, _, _| {
14997        if editor.context_menu.borrow_mut().is_some() {
14998            panic!(
14999                "expected completion menu to be hidden, as words completion threshold is not met still"
15000            );
15001        }
15002    });
15003
15004    cx.simulate_keystroke("w");
15005    cx.executor().run_until_parked();
15006    cx.update_editor(|editor, _, _| {
15007        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15008        {
15009            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
15010        } else {
15011            panic!("expected completion menu to be open after the word completions threshold is met");
15012        }
15013    });
15014}
15015
15016#[gpui::test]
15017async fn test_word_completions_disabled(cx: &mut TestAppContext) {
15018    init_test(cx, |language_settings| {
15019        language_settings.defaults.completions = Some(CompletionSettingsContent {
15020            words: Some(WordsCompletionMode::Enabled),
15021            words_min_length: Some(0),
15022            lsp_insert_mode: Some(LspInsertMode::Insert),
15023            ..Default::default()
15024        });
15025    });
15026
15027    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15028    cx.update_editor(|editor, _, _| {
15029        editor.disable_word_completions();
15030    });
15031    cx.set_state(indoc! {"ˇ
15032        wow
15033        wowen
15034        wowser
15035    "});
15036    cx.simulate_keystroke("w");
15037    cx.executor().run_until_parked();
15038    cx.update_editor(|editor, _, _| {
15039        if editor.context_menu.borrow_mut().is_some() {
15040            panic!(
15041                "expected completion menu to be hidden, as words completion are disabled for this editor"
15042            );
15043        }
15044    });
15045
15046    cx.update_editor(|editor, window, cx| {
15047        editor.show_word_completions(&ShowWordCompletions, window, cx);
15048    });
15049    cx.executor().run_until_parked();
15050    cx.update_editor(|editor, _, _| {
15051        if editor.context_menu.borrow_mut().is_some() {
15052            panic!(
15053                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
15054            );
15055        }
15056    });
15057}
15058
15059fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
15060    let position = || lsp::Position {
15061        line: params.text_document_position.position.line,
15062        character: params.text_document_position.position.character,
15063    };
15064    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15065        range: lsp::Range {
15066            start: position(),
15067            end: position(),
15068        },
15069        new_text: text.to_string(),
15070    }))
15071}
15072
15073#[gpui::test]
15074async fn test_multiline_completion(cx: &mut TestAppContext) {
15075    init_test(cx, |_| {});
15076
15077    let fs = FakeFs::new(cx.executor());
15078    fs.insert_tree(
15079        path!("/a"),
15080        json!({
15081            "main.ts": "a",
15082        }),
15083    )
15084    .await;
15085
15086    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15087    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15088    let typescript_language = Arc::new(Language::new(
15089        LanguageConfig {
15090            name: "TypeScript".into(),
15091            matcher: LanguageMatcher {
15092                path_suffixes: vec!["ts".to_string()],
15093                ..LanguageMatcher::default()
15094            },
15095            line_comments: vec!["// ".into()],
15096            ..LanguageConfig::default()
15097        },
15098        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
15099    ));
15100    language_registry.add(typescript_language.clone());
15101    let mut fake_servers = language_registry.register_fake_lsp(
15102        "TypeScript",
15103        FakeLspAdapter {
15104            capabilities: lsp::ServerCapabilities {
15105                completion_provider: Some(lsp::CompletionOptions {
15106                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15107                    ..lsp::CompletionOptions::default()
15108                }),
15109                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15110                ..lsp::ServerCapabilities::default()
15111            },
15112            // Emulate vtsls label generation
15113            label_for_completion: Some(Box::new(|item, _| {
15114                let text = if let Some(description) = item
15115                    .label_details
15116                    .as_ref()
15117                    .and_then(|label_details| label_details.description.as_ref())
15118                {
15119                    format!("{} {}", item.label, description)
15120                } else if let Some(detail) = &item.detail {
15121                    format!("{} {}", item.label, detail)
15122                } else {
15123                    item.label.clone()
15124                };
15125                Some(language::CodeLabel::plain(text, None))
15126            })),
15127            ..FakeLspAdapter::default()
15128        },
15129    );
15130    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15131    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15132    let worktree_id = workspace
15133        .update(cx, |workspace, _window, cx| {
15134            workspace.project().update(cx, |project, cx| {
15135                project.worktrees(cx).next().unwrap().read(cx).id()
15136            })
15137        })
15138        .unwrap();
15139    let _buffer = project
15140        .update(cx, |project, cx| {
15141            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
15142        })
15143        .await
15144        .unwrap();
15145    let editor = workspace
15146        .update(cx, |workspace, window, cx| {
15147            workspace.open_path((worktree_id, rel_path("main.ts")), None, true, window, cx)
15148        })
15149        .unwrap()
15150        .await
15151        .unwrap()
15152        .downcast::<Editor>()
15153        .unwrap();
15154    let fake_server = fake_servers.next().await.unwrap();
15155
15156    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
15157    let multiline_label_2 = "a\nb\nc\n";
15158    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
15159    let multiline_description = "d\ne\nf\n";
15160    let multiline_detail_2 = "g\nh\ni\n";
15161
15162    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
15163        move |params, _| async move {
15164            Ok(Some(lsp::CompletionResponse::Array(vec![
15165                lsp::CompletionItem {
15166                    label: multiline_label.to_string(),
15167                    text_edit: gen_text_edit(&params, "new_text_1"),
15168                    ..lsp::CompletionItem::default()
15169                },
15170                lsp::CompletionItem {
15171                    label: "single line label 1".to_string(),
15172                    detail: Some(multiline_detail.to_string()),
15173                    text_edit: gen_text_edit(&params, "new_text_2"),
15174                    ..lsp::CompletionItem::default()
15175                },
15176                lsp::CompletionItem {
15177                    label: "single line label 2".to_string(),
15178                    label_details: Some(lsp::CompletionItemLabelDetails {
15179                        description: Some(multiline_description.to_string()),
15180                        detail: None,
15181                    }),
15182                    text_edit: gen_text_edit(&params, "new_text_2"),
15183                    ..lsp::CompletionItem::default()
15184                },
15185                lsp::CompletionItem {
15186                    label: multiline_label_2.to_string(),
15187                    detail: Some(multiline_detail_2.to_string()),
15188                    text_edit: gen_text_edit(&params, "new_text_3"),
15189                    ..lsp::CompletionItem::default()
15190                },
15191                lsp::CompletionItem {
15192                    label: "Label with many     spaces and \t but without newlines".to_string(),
15193                    detail: Some(
15194                        "Details with many     spaces and \t but without newlines".to_string(),
15195                    ),
15196                    text_edit: gen_text_edit(&params, "new_text_4"),
15197                    ..lsp::CompletionItem::default()
15198                },
15199            ])))
15200        },
15201    );
15202
15203    editor.update_in(cx, |editor, window, cx| {
15204        cx.focus_self(window);
15205        editor.move_to_end(&MoveToEnd, window, cx);
15206        editor.handle_input(".", window, cx);
15207    });
15208    cx.run_until_parked();
15209    completion_handle.next().await.unwrap();
15210
15211    editor.update(cx, |editor, _| {
15212        assert!(editor.context_menu_visible());
15213        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15214        {
15215            let completion_labels = menu
15216                .completions
15217                .borrow()
15218                .iter()
15219                .map(|c| c.label.text.clone())
15220                .collect::<Vec<_>>();
15221            assert_eq!(
15222                completion_labels,
15223                &[
15224                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
15225                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
15226                    "single line label 2 d e f ",
15227                    "a b c g h i ",
15228                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
15229                ],
15230                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
15231            );
15232
15233            for completion in menu
15234                .completions
15235                .borrow()
15236                .iter() {
15237                    assert_eq!(
15238                        completion.label.filter_range,
15239                        0..completion.label.text.len(),
15240                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
15241                    );
15242                }
15243        } else {
15244            panic!("expected completion menu to be open");
15245        }
15246    });
15247}
15248
15249#[gpui::test]
15250async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
15251    init_test(cx, |_| {});
15252    let mut cx = EditorLspTestContext::new_rust(
15253        lsp::ServerCapabilities {
15254            completion_provider: Some(lsp::CompletionOptions {
15255                trigger_characters: Some(vec![".".to_string()]),
15256                ..Default::default()
15257            }),
15258            ..Default::default()
15259        },
15260        cx,
15261    )
15262    .await;
15263    cx.lsp
15264        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15265            Ok(Some(lsp::CompletionResponse::Array(vec![
15266                lsp::CompletionItem {
15267                    label: "first".into(),
15268                    ..Default::default()
15269                },
15270                lsp::CompletionItem {
15271                    label: "last".into(),
15272                    ..Default::default()
15273                },
15274            ])))
15275        });
15276    cx.set_state("variableˇ");
15277    cx.simulate_keystroke(".");
15278    cx.executor().run_until_parked();
15279
15280    cx.update_editor(|editor, _, _| {
15281        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15282        {
15283            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
15284        } else {
15285            panic!("expected completion menu to be open");
15286        }
15287    });
15288
15289    cx.update_editor(|editor, window, cx| {
15290        editor.move_page_down(&MovePageDown::default(), window, cx);
15291        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15292        {
15293            assert!(
15294                menu.selected_item == 1,
15295                "expected PageDown to select the last item from the context menu"
15296            );
15297        } else {
15298            panic!("expected completion menu to stay open after PageDown");
15299        }
15300    });
15301
15302    cx.update_editor(|editor, window, cx| {
15303        editor.move_page_up(&MovePageUp::default(), window, cx);
15304        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15305        {
15306            assert!(
15307                menu.selected_item == 0,
15308                "expected PageUp to select the first item from the context menu"
15309            );
15310        } else {
15311            panic!("expected completion menu to stay open after PageUp");
15312        }
15313    });
15314}
15315
15316#[gpui::test]
15317async fn test_as_is_completions(cx: &mut TestAppContext) {
15318    init_test(cx, |_| {});
15319    let mut cx = EditorLspTestContext::new_rust(
15320        lsp::ServerCapabilities {
15321            completion_provider: Some(lsp::CompletionOptions {
15322                ..Default::default()
15323            }),
15324            ..Default::default()
15325        },
15326        cx,
15327    )
15328    .await;
15329    cx.lsp
15330        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15331            Ok(Some(lsp::CompletionResponse::Array(vec![
15332                lsp::CompletionItem {
15333                    label: "unsafe".into(),
15334                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15335                        range: lsp::Range {
15336                            start: lsp::Position {
15337                                line: 1,
15338                                character: 2,
15339                            },
15340                            end: lsp::Position {
15341                                line: 1,
15342                                character: 3,
15343                            },
15344                        },
15345                        new_text: "unsafe".to_string(),
15346                    })),
15347                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
15348                    ..Default::default()
15349                },
15350            ])))
15351        });
15352    cx.set_state("fn a() {}\n");
15353    cx.executor().run_until_parked();
15354    cx.update_editor(|editor, window, cx| {
15355        editor.show_completions(
15356            &ShowCompletions {
15357                trigger: Some("\n".into()),
15358            },
15359            window,
15360            cx,
15361        );
15362    });
15363    cx.executor().run_until_parked();
15364
15365    cx.update_editor(|editor, window, cx| {
15366        editor.confirm_completion(&Default::default(), window, cx)
15367    });
15368    cx.executor().run_until_parked();
15369    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
15370}
15371
15372#[gpui::test]
15373async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
15374    init_test(cx, |_| {});
15375    let language =
15376        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
15377    let mut cx = EditorLspTestContext::new(
15378        language,
15379        lsp::ServerCapabilities {
15380            completion_provider: Some(lsp::CompletionOptions {
15381                ..lsp::CompletionOptions::default()
15382            }),
15383            ..lsp::ServerCapabilities::default()
15384        },
15385        cx,
15386    )
15387    .await;
15388
15389    cx.set_state(
15390        "#ifndef BAR_H
15391#define BAR_H
15392
15393#include <stdbool.h>
15394
15395int fn_branch(bool do_branch1, bool do_branch2);
15396
15397#endif // BAR_H
15398ˇ",
15399    );
15400    cx.executor().run_until_parked();
15401    cx.update_editor(|editor, window, cx| {
15402        editor.handle_input("#", window, cx);
15403    });
15404    cx.executor().run_until_parked();
15405    cx.update_editor(|editor, window, cx| {
15406        editor.handle_input("i", window, cx);
15407    });
15408    cx.executor().run_until_parked();
15409    cx.update_editor(|editor, window, cx| {
15410        editor.handle_input("n", window, cx);
15411    });
15412    cx.executor().run_until_parked();
15413    cx.assert_editor_state(
15414        "#ifndef BAR_H
15415#define BAR_H
15416
15417#include <stdbool.h>
15418
15419int fn_branch(bool do_branch1, bool do_branch2);
15420
15421#endif // BAR_H
15422#inˇ",
15423    );
15424
15425    cx.lsp
15426        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15427            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15428                is_incomplete: false,
15429                item_defaults: None,
15430                items: vec![lsp::CompletionItem {
15431                    kind: Some(lsp::CompletionItemKind::SNIPPET),
15432                    label_details: Some(lsp::CompletionItemLabelDetails {
15433                        detail: Some("header".to_string()),
15434                        description: None,
15435                    }),
15436                    label: " include".to_string(),
15437                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15438                        range: lsp::Range {
15439                            start: lsp::Position {
15440                                line: 8,
15441                                character: 1,
15442                            },
15443                            end: lsp::Position {
15444                                line: 8,
15445                                character: 1,
15446                            },
15447                        },
15448                        new_text: "include \"$0\"".to_string(),
15449                    })),
15450                    sort_text: Some("40b67681include".to_string()),
15451                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15452                    filter_text: Some("include".to_string()),
15453                    insert_text: Some("include \"$0\"".to_string()),
15454                    ..lsp::CompletionItem::default()
15455                }],
15456            })))
15457        });
15458    cx.update_editor(|editor, window, cx| {
15459        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15460    });
15461    cx.executor().run_until_parked();
15462    cx.update_editor(|editor, window, cx| {
15463        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15464    });
15465    cx.executor().run_until_parked();
15466    cx.assert_editor_state(
15467        "#ifndef BAR_H
15468#define BAR_H
15469
15470#include <stdbool.h>
15471
15472int fn_branch(bool do_branch1, bool do_branch2);
15473
15474#endif // BAR_H
15475#include \"ˇ\"",
15476    );
15477
15478    cx.lsp
15479        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15480            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15481                is_incomplete: true,
15482                item_defaults: None,
15483                items: vec![lsp::CompletionItem {
15484                    kind: Some(lsp::CompletionItemKind::FILE),
15485                    label: "AGL/".to_string(),
15486                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15487                        range: lsp::Range {
15488                            start: lsp::Position {
15489                                line: 8,
15490                                character: 10,
15491                            },
15492                            end: lsp::Position {
15493                                line: 8,
15494                                character: 11,
15495                            },
15496                        },
15497                        new_text: "AGL/".to_string(),
15498                    })),
15499                    sort_text: Some("40b67681AGL/".to_string()),
15500                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15501                    filter_text: Some("AGL/".to_string()),
15502                    insert_text: Some("AGL/".to_string()),
15503                    ..lsp::CompletionItem::default()
15504                }],
15505            })))
15506        });
15507    cx.update_editor(|editor, window, cx| {
15508        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15509    });
15510    cx.executor().run_until_parked();
15511    cx.update_editor(|editor, window, cx| {
15512        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15513    });
15514    cx.executor().run_until_parked();
15515    cx.assert_editor_state(
15516        r##"#ifndef BAR_H
15517#define BAR_H
15518
15519#include <stdbool.h>
15520
15521int fn_branch(bool do_branch1, bool do_branch2);
15522
15523#endif // BAR_H
15524#include "AGL/ˇ"##,
15525    );
15526
15527    cx.update_editor(|editor, window, cx| {
15528        editor.handle_input("\"", window, cx);
15529    });
15530    cx.executor().run_until_parked();
15531    cx.assert_editor_state(
15532        r##"#ifndef BAR_H
15533#define BAR_H
15534
15535#include <stdbool.h>
15536
15537int fn_branch(bool do_branch1, bool do_branch2);
15538
15539#endif // BAR_H
15540#include "AGL/"ˇ"##,
15541    );
15542}
15543
15544#[gpui::test]
15545async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
15546    init_test(cx, |_| {});
15547
15548    let mut cx = EditorLspTestContext::new_rust(
15549        lsp::ServerCapabilities {
15550            completion_provider: Some(lsp::CompletionOptions {
15551                trigger_characters: Some(vec![".".to_string()]),
15552                resolve_provider: Some(true),
15553                ..Default::default()
15554            }),
15555            ..Default::default()
15556        },
15557        cx,
15558    )
15559    .await;
15560
15561    cx.set_state("fn main() { let a = 2ˇ; }");
15562    cx.simulate_keystroke(".");
15563    let completion_item = lsp::CompletionItem {
15564        label: "Some".into(),
15565        kind: Some(lsp::CompletionItemKind::SNIPPET),
15566        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15567        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15568            kind: lsp::MarkupKind::Markdown,
15569            value: "```rust\nSome(2)\n```".to_string(),
15570        })),
15571        deprecated: Some(false),
15572        sort_text: Some("Some".to_string()),
15573        filter_text: Some("Some".to_string()),
15574        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15575        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15576            range: lsp::Range {
15577                start: lsp::Position {
15578                    line: 0,
15579                    character: 22,
15580                },
15581                end: lsp::Position {
15582                    line: 0,
15583                    character: 22,
15584                },
15585            },
15586            new_text: "Some(2)".to_string(),
15587        })),
15588        additional_text_edits: Some(vec![lsp::TextEdit {
15589            range: lsp::Range {
15590                start: lsp::Position {
15591                    line: 0,
15592                    character: 20,
15593                },
15594                end: lsp::Position {
15595                    line: 0,
15596                    character: 22,
15597                },
15598            },
15599            new_text: "".to_string(),
15600        }]),
15601        ..Default::default()
15602    };
15603
15604    let closure_completion_item = completion_item.clone();
15605    let counter = Arc::new(AtomicUsize::new(0));
15606    let counter_clone = counter.clone();
15607    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15608        let task_completion_item = closure_completion_item.clone();
15609        counter_clone.fetch_add(1, atomic::Ordering::Release);
15610        async move {
15611            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15612                is_incomplete: true,
15613                item_defaults: None,
15614                items: vec![task_completion_item],
15615            })))
15616        }
15617    });
15618
15619    cx.condition(|editor, _| editor.context_menu_visible())
15620        .await;
15621    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
15622    assert!(request.next().await.is_some());
15623    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15624
15625    cx.simulate_keystrokes("S o m");
15626    cx.condition(|editor, _| editor.context_menu_visible())
15627        .await;
15628    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
15629    assert!(request.next().await.is_some());
15630    assert!(request.next().await.is_some());
15631    assert!(request.next().await.is_some());
15632    request.close();
15633    assert!(request.next().await.is_none());
15634    assert_eq!(
15635        counter.load(atomic::Ordering::Acquire),
15636        4,
15637        "With the completions menu open, only one LSP request should happen per input"
15638    );
15639}
15640
15641#[gpui::test]
15642async fn test_toggle_comment(cx: &mut TestAppContext) {
15643    init_test(cx, |_| {});
15644    let mut cx = EditorTestContext::new(cx).await;
15645    let language = Arc::new(Language::new(
15646        LanguageConfig {
15647            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15648            ..Default::default()
15649        },
15650        Some(tree_sitter_rust::LANGUAGE.into()),
15651    ));
15652    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15653
15654    // If multiple selections intersect a line, the line is only toggled once.
15655    cx.set_state(indoc! {"
15656        fn a() {
15657            «//b();
15658            ˇ»// «c();
15659            //ˇ»  d();
15660        }
15661    "});
15662
15663    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15664
15665    cx.assert_editor_state(indoc! {"
15666        fn a() {
15667            «b();
15668            c();
15669            ˇ» d();
15670        }
15671    "});
15672
15673    // The comment prefix is inserted at the same column for every line in a
15674    // selection.
15675    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15676
15677    cx.assert_editor_state(indoc! {"
15678        fn a() {
15679            // «b();
15680            // c();
15681            ˇ»//  d();
15682        }
15683    "});
15684
15685    // If a selection ends at the beginning of a line, that line is not toggled.
15686    cx.set_selections_state(indoc! {"
15687        fn a() {
15688            // b();
15689            «// c();
15690        ˇ»    //  d();
15691        }
15692    "});
15693
15694    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15695
15696    cx.assert_editor_state(indoc! {"
15697        fn a() {
15698            // b();
15699            «c();
15700        ˇ»    //  d();
15701        }
15702    "});
15703
15704    // If a selection span a single line and is empty, the line is toggled.
15705    cx.set_state(indoc! {"
15706        fn a() {
15707            a();
15708            b();
15709        ˇ
15710        }
15711    "});
15712
15713    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15714
15715    cx.assert_editor_state(indoc! {"
15716        fn a() {
15717            a();
15718            b();
15719        //•ˇ
15720        }
15721    "});
15722
15723    // If a selection span multiple lines, empty lines are not toggled.
15724    cx.set_state(indoc! {"
15725        fn a() {
15726            «a();
15727
15728            c();ˇ»
15729        }
15730    "});
15731
15732    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15733
15734    cx.assert_editor_state(indoc! {"
15735        fn a() {
15736            // «a();
15737
15738            // c();ˇ»
15739        }
15740    "});
15741
15742    // If a selection includes multiple comment prefixes, all lines are uncommented.
15743    cx.set_state(indoc! {"
15744        fn a() {
15745            «// a();
15746            /// b();
15747            //! c();ˇ»
15748        }
15749    "});
15750
15751    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15752
15753    cx.assert_editor_state(indoc! {"
15754        fn a() {
15755            «a();
15756            b();
15757            c();ˇ»
15758        }
15759    "});
15760}
15761
15762#[gpui::test]
15763async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
15764    init_test(cx, |_| {});
15765    let mut cx = EditorTestContext::new(cx).await;
15766    let language = Arc::new(Language::new(
15767        LanguageConfig {
15768            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15769            ..Default::default()
15770        },
15771        Some(tree_sitter_rust::LANGUAGE.into()),
15772    ));
15773    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15774
15775    let toggle_comments = &ToggleComments {
15776        advance_downwards: false,
15777        ignore_indent: true,
15778    };
15779
15780    // If multiple selections intersect a line, the line is only toggled once.
15781    cx.set_state(indoc! {"
15782        fn a() {
15783        //    «b();
15784        //    c();
15785        //    ˇ» d();
15786        }
15787    "});
15788
15789    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15790
15791    cx.assert_editor_state(indoc! {"
15792        fn a() {
15793            «b();
15794            c();
15795            ˇ» d();
15796        }
15797    "});
15798
15799    // The comment prefix is inserted at the beginning of each line
15800    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15801
15802    cx.assert_editor_state(indoc! {"
15803        fn a() {
15804        //    «b();
15805        //    c();
15806        //    ˇ» d();
15807        }
15808    "});
15809
15810    // If a selection ends at the beginning of a line, that line is not toggled.
15811    cx.set_selections_state(indoc! {"
15812        fn a() {
15813        //    b();
15814        //    «c();
15815        ˇ»//     d();
15816        }
15817    "});
15818
15819    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15820
15821    cx.assert_editor_state(indoc! {"
15822        fn a() {
15823        //    b();
15824            «c();
15825        ˇ»//     d();
15826        }
15827    "});
15828
15829    // If a selection span a single line and is empty, the line is toggled.
15830    cx.set_state(indoc! {"
15831        fn a() {
15832            a();
15833            b();
15834        ˇ
15835        }
15836    "});
15837
15838    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15839
15840    cx.assert_editor_state(indoc! {"
15841        fn a() {
15842            a();
15843            b();
15844        //ˇ
15845        }
15846    "});
15847
15848    // If a selection span multiple lines, empty lines are not toggled.
15849    cx.set_state(indoc! {"
15850        fn a() {
15851            «a();
15852
15853            c();ˇ»
15854        }
15855    "});
15856
15857    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15858
15859    cx.assert_editor_state(indoc! {"
15860        fn a() {
15861        //    «a();
15862
15863        //    c();ˇ»
15864        }
15865    "});
15866
15867    // If a selection includes multiple comment prefixes, all lines are uncommented.
15868    cx.set_state(indoc! {"
15869        fn a() {
15870        //    «a();
15871        ///    b();
15872        //!    c();ˇ»
15873        }
15874    "});
15875
15876    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15877
15878    cx.assert_editor_state(indoc! {"
15879        fn a() {
15880            «a();
15881            b();
15882            c();ˇ»
15883        }
15884    "});
15885}
15886
15887#[gpui::test]
15888async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
15889    init_test(cx, |_| {});
15890
15891    let language = Arc::new(Language::new(
15892        LanguageConfig {
15893            line_comments: vec!["// ".into()],
15894            ..Default::default()
15895        },
15896        Some(tree_sitter_rust::LANGUAGE.into()),
15897    ));
15898
15899    let mut cx = EditorTestContext::new(cx).await;
15900
15901    cx.language_registry().add(language.clone());
15902    cx.update_buffer(|buffer, cx| {
15903        buffer.set_language(Some(language), cx);
15904    });
15905
15906    let toggle_comments = &ToggleComments {
15907        advance_downwards: true,
15908        ignore_indent: false,
15909    };
15910
15911    // Single cursor on one line -> advance
15912    // Cursor moves horizontally 3 characters as well on non-blank line
15913    cx.set_state(indoc!(
15914        "fn a() {
15915             ˇdog();
15916             cat();
15917        }"
15918    ));
15919    cx.update_editor(|editor, window, cx| {
15920        editor.toggle_comments(toggle_comments, window, cx);
15921    });
15922    cx.assert_editor_state(indoc!(
15923        "fn a() {
15924             // dog();
15925             catˇ();
15926        }"
15927    ));
15928
15929    // Single selection on one line -> don't advance
15930    cx.set_state(indoc!(
15931        "fn a() {
15932             «dog()ˇ»;
15933             cat();
15934        }"
15935    ));
15936    cx.update_editor(|editor, window, cx| {
15937        editor.toggle_comments(toggle_comments, window, cx);
15938    });
15939    cx.assert_editor_state(indoc!(
15940        "fn a() {
15941             // «dog()ˇ»;
15942             cat();
15943        }"
15944    ));
15945
15946    // Multiple cursors on one line -> advance
15947    cx.set_state(indoc!(
15948        "fn a() {
15949             ˇdˇog();
15950             cat();
15951        }"
15952    ));
15953    cx.update_editor(|editor, window, cx| {
15954        editor.toggle_comments(toggle_comments, window, cx);
15955    });
15956    cx.assert_editor_state(indoc!(
15957        "fn a() {
15958             // dog();
15959             catˇ(ˇ);
15960        }"
15961    ));
15962
15963    // Multiple cursors on one line, with selection -> don't advance
15964    cx.set_state(indoc!(
15965        "fn a() {
15966             ˇdˇog«()ˇ»;
15967             cat();
15968        }"
15969    ));
15970    cx.update_editor(|editor, window, cx| {
15971        editor.toggle_comments(toggle_comments, window, cx);
15972    });
15973    cx.assert_editor_state(indoc!(
15974        "fn a() {
15975             // ˇdˇog«()ˇ»;
15976             cat();
15977        }"
15978    ));
15979
15980    // Single cursor on one line -> advance
15981    // Cursor moves to column 0 on blank line
15982    cx.set_state(indoc!(
15983        "fn a() {
15984             ˇdog();
15985
15986             cat();
15987        }"
15988    ));
15989    cx.update_editor(|editor, window, cx| {
15990        editor.toggle_comments(toggle_comments, window, cx);
15991    });
15992    cx.assert_editor_state(indoc!(
15993        "fn a() {
15994             // dog();
15995        ˇ
15996             cat();
15997        }"
15998    ));
15999
16000    // Single cursor on one line -> advance
16001    // Cursor starts and ends at column 0
16002    cx.set_state(indoc!(
16003        "fn a() {
16004         ˇ    dog();
16005             cat();
16006        }"
16007    ));
16008    cx.update_editor(|editor, window, cx| {
16009        editor.toggle_comments(toggle_comments, window, cx);
16010    });
16011    cx.assert_editor_state(indoc!(
16012        "fn a() {
16013             // dog();
16014         ˇ    cat();
16015        }"
16016    ));
16017}
16018
16019#[gpui::test]
16020async fn test_toggle_block_comment(cx: &mut TestAppContext) {
16021    init_test(cx, |_| {});
16022
16023    let mut cx = EditorTestContext::new(cx).await;
16024
16025    let html_language = Arc::new(
16026        Language::new(
16027            LanguageConfig {
16028                name: "HTML".into(),
16029                block_comment: Some(BlockCommentConfig {
16030                    start: "<!-- ".into(),
16031                    prefix: "".into(),
16032                    end: " -->".into(),
16033                    tab_size: 0,
16034                }),
16035                ..Default::default()
16036            },
16037            Some(tree_sitter_html::LANGUAGE.into()),
16038        )
16039        .with_injection_query(
16040            r#"
16041            (script_element
16042                (raw_text) @injection.content
16043                (#set! injection.language "javascript"))
16044            "#,
16045        )
16046        .unwrap(),
16047    );
16048
16049    let javascript_language = Arc::new(Language::new(
16050        LanguageConfig {
16051            name: "JavaScript".into(),
16052            line_comments: vec!["// ".into()],
16053            ..Default::default()
16054        },
16055        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
16056    ));
16057
16058    cx.language_registry().add(html_language.clone());
16059    cx.language_registry().add(javascript_language);
16060    cx.update_buffer(|buffer, cx| {
16061        buffer.set_language(Some(html_language), cx);
16062    });
16063
16064    // Toggle comments for empty selections
16065    cx.set_state(
16066        &r#"
16067            <p>A</p>ˇ
16068            <p>B</p>ˇ
16069            <p>C</p>ˇ
16070        "#
16071        .unindent(),
16072    );
16073    cx.update_editor(|editor, window, cx| {
16074        editor.toggle_comments(&ToggleComments::default(), window, cx)
16075    });
16076    cx.assert_editor_state(
16077        &r#"
16078            <!-- <p>A</p>ˇ -->
16079            <!-- <p>B</p>ˇ -->
16080            <!-- <p>C</p>ˇ -->
16081        "#
16082        .unindent(),
16083    );
16084    cx.update_editor(|editor, window, cx| {
16085        editor.toggle_comments(&ToggleComments::default(), window, cx)
16086    });
16087    cx.assert_editor_state(
16088        &r#"
16089            <p>A</p>ˇ
16090            <p>B</p>ˇ
16091            <p>C</p>ˇ
16092        "#
16093        .unindent(),
16094    );
16095
16096    // Toggle comments for mixture of empty and non-empty selections, where
16097    // multiple selections occupy a given line.
16098    cx.set_state(
16099        &r#"
16100            <p>A«</p>
16101            <p>ˇ»B</p>ˇ
16102            <p>C«</p>
16103            <p>ˇ»D</p>ˇ
16104        "#
16105        .unindent(),
16106    );
16107
16108    cx.update_editor(|editor, window, cx| {
16109        editor.toggle_comments(&ToggleComments::default(), window, cx)
16110    });
16111    cx.assert_editor_state(
16112        &r#"
16113            <!-- <p>A«</p>
16114            <p>ˇ»B</p>ˇ -->
16115            <!-- <p>C«</p>
16116            <p>ˇ»D</p>ˇ -->
16117        "#
16118        .unindent(),
16119    );
16120    cx.update_editor(|editor, window, cx| {
16121        editor.toggle_comments(&ToggleComments::default(), window, cx)
16122    });
16123    cx.assert_editor_state(
16124        &r#"
16125            <p>A«</p>
16126            <p>ˇ»B</p>ˇ
16127            <p>C«</p>
16128            <p>ˇ»D</p>ˇ
16129        "#
16130        .unindent(),
16131    );
16132
16133    // Toggle comments when different languages are active for different
16134    // selections.
16135    cx.set_state(
16136        &r#"
16137            ˇ<script>
16138                ˇvar x = new Y();
16139            ˇ</script>
16140        "#
16141        .unindent(),
16142    );
16143    cx.executor().run_until_parked();
16144    cx.update_editor(|editor, window, cx| {
16145        editor.toggle_comments(&ToggleComments::default(), window, cx)
16146    });
16147    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
16148    // Uncommenting and commenting from this position brings in even more wrong artifacts.
16149    cx.assert_editor_state(
16150        &r#"
16151            <!-- ˇ<script> -->
16152                // ˇvar x = new Y();
16153            <!-- ˇ</script> -->
16154        "#
16155        .unindent(),
16156    );
16157}
16158
16159#[gpui::test]
16160fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
16161    init_test(cx, |_| {});
16162
16163    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16164    let multibuffer = cx.new(|cx| {
16165        let mut multibuffer = MultiBuffer::new(ReadWrite);
16166        multibuffer.push_excerpts(
16167            buffer.clone(),
16168            [
16169                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
16170                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
16171            ],
16172            cx,
16173        );
16174        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
16175        multibuffer
16176    });
16177
16178    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16179    editor.update_in(cx, |editor, window, cx| {
16180        assert_eq!(editor.text(cx), "aaaa\nbbbb");
16181        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16182            s.select_ranges([
16183                Point::new(0, 0)..Point::new(0, 0),
16184                Point::new(1, 0)..Point::new(1, 0),
16185            ])
16186        });
16187
16188        editor.handle_input("X", window, cx);
16189        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
16190        assert_eq!(
16191            editor.selections.ranges(&editor.display_snapshot(cx)),
16192            [
16193                Point::new(0, 1)..Point::new(0, 1),
16194                Point::new(1, 1)..Point::new(1, 1),
16195            ]
16196        );
16197
16198        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
16199        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16200            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
16201        });
16202        editor.backspace(&Default::default(), window, cx);
16203        assert_eq!(editor.text(cx), "Xa\nbbb");
16204        assert_eq!(
16205            editor.selections.ranges(&editor.display_snapshot(cx)),
16206            [Point::new(1, 0)..Point::new(1, 0)]
16207        );
16208
16209        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16210            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
16211        });
16212        editor.backspace(&Default::default(), window, cx);
16213        assert_eq!(editor.text(cx), "X\nbb");
16214        assert_eq!(
16215            editor.selections.ranges(&editor.display_snapshot(cx)),
16216            [Point::new(0, 1)..Point::new(0, 1)]
16217        );
16218    });
16219}
16220
16221#[gpui::test]
16222fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
16223    init_test(cx, |_| {});
16224
16225    let markers = vec![('[', ']').into(), ('(', ')').into()];
16226    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
16227        indoc! {"
16228            [aaaa
16229            (bbbb]
16230            cccc)",
16231        },
16232        markers.clone(),
16233    );
16234    let excerpt_ranges = markers.into_iter().map(|marker| {
16235        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
16236        ExcerptRange::new(context)
16237    });
16238    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
16239    let multibuffer = cx.new(|cx| {
16240        let mut multibuffer = MultiBuffer::new(ReadWrite);
16241        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
16242        multibuffer
16243    });
16244
16245    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16246    editor.update_in(cx, |editor, window, cx| {
16247        let (expected_text, selection_ranges) = marked_text_ranges(
16248            indoc! {"
16249                aaaa
16250                bˇbbb
16251                bˇbbˇb
16252                cccc"
16253            },
16254            true,
16255        );
16256        assert_eq!(editor.text(cx), expected_text);
16257        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16258            s.select_ranges(selection_ranges)
16259        });
16260
16261        editor.handle_input("X", window, cx);
16262
16263        let (expected_text, expected_selections) = marked_text_ranges(
16264            indoc! {"
16265                aaaa
16266                bXˇbbXb
16267                bXˇbbXˇb
16268                cccc"
16269            },
16270            false,
16271        );
16272        assert_eq!(editor.text(cx), expected_text);
16273        assert_eq!(
16274            editor.selections.ranges(&editor.display_snapshot(cx)),
16275            expected_selections
16276        );
16277
16278        editor.newline(&Newline, window, cx);
16279        let (expected_text, expected_selections) = marked_text_ranges(
16280            indoc! {"
16281                aaaa
16282                bX
16283                ˇbbX
16284                b
16285                bX
16286                ˇbbX
16287                ˇb
16288                cccc"
16289            },
16290            false,
16291        );
16292        assert_eq!(editor.text(cx), expected_text);
16293        assert_eq!(
16294            editor.selections.ranges(&editor.display_snapshot(cx)),
16295            expected_selections
16296        );
16297    });
16298}
16299
16300#[gpui::test]
16301fn test_refresh_selections(cx: &mut TestAppContext) {
16302    init_test(cx, |_| {});
16303
16304    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16305    let mut excerpt1_id = None;
16306    let multibuffer = cx.new(|cx| {
16307        let mut multibuffer = MultiBuffer::new(ReadWrite);
16308        excerpt1_id = multibuffer
16309            .push_excerpts(
16310                buffer.clone(),
16311                [
16312                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16313                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16314                ],
16315                cx,
16316            )
16317            .into_iter()
16318            .next();
16319        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16320        multibuffer
16321    });
16322
16323    let editor = cx.add_window(|window, cx| {
16324        let mut editor = build_editor(multibuffer.clone(), window, cx);
16325        let snapshot = editor.snapshot(window, cx);
16326        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16327            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
16328        });
16329        editor.begin_selection(
16330            Point::new(2, 1).to_display_point(&snapshot),
16331            true,
16332            1,
16333            window,
16334            cx,
16335        );
16336        assert_eq!(
16337            editor.selections.ranges(&editor.display_snapshot(cx)),
16338            [
16339                Point::new(1, 3)..Point::new(1, 3),
16340                Point::new(2, 1)..Point::new(2, 1),
16341            ]
16342        );
16343        editor
16344    });
16345
16346    // Refreshing selections is a no-op when excerpts haven't changed.
16347    _ = editor.update(cx, |editor, window, cx| {
16348        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16349        assert_eq!(
16350            editor.selections.ranges(&editor.display_snapshot(cx)),
16351            [
16352                Point::new(1, 3)..Point::new(1, 3),
16353                Point::new(2, 1)..Point::new(2, 1),
16354            ]
16355        );
16356    });
16357
16358    multibuffer.update(cx, |multibuffer, cx| {
16359        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16360    });
16361    _ = editor.update(cx, |editor, window, cx| {
16362        // Removing an excerpt causes the first selection to become degenerate.
16363        assert_eq!(
16364            editor.selections.ranges(&editor.display_snapshot(cx)),
16365            [
16366                Point::new(0, 0)..Point::new(0, 0),
16367                Point::new(0, 1)..Point::new(0, 1)
16368            ]
16369        );
16370
16371        // Refreshing selections will relocate the first selection to the original buffer
16372        // location.
16373        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16374        assert_eq!(
16375            editor.selections.ranges(&editor.display_snapshot(cx)),
16376            [
16377                Point::new(0, 1)..Point::new(0, 1),
16378                Point::new(0, 3)..Point::new(0, 3)
16379            ]
16380        );
16381        assert!(editor.selections.pending_anchor().is_some());
16382    });
16383}
16384
16385#[gpui::test]
16386fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
16387    init_test(cx, |_| {});
16388
16389    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16390    let mut excerpt1_id = None;
16391    let multibuffer = cx.new(|cx| {
16392        let mut multibuffer = MultiBuffer::new(ReadWrite);
16393        excerpt1_id = multibuffer
16394            .push_excerpts(
16395                buffer.clone(),
16396                [
16397                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16398                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16399                ],
16400                cx,
16401            )
16402            .into_iter()
16403            .next();
16404        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16405        multibuffer
16406    });
16407
16408    let editor = cx.add_window(|window, cx| {
16409        let mut editor = build_editor(multibuffer.clone(), window, cx);
16410        let snapshot = editor.snapshot(window, cx);
16411        editor.begin_selection(
16412            Point::new(1, 3).to_display_point(&snapshot),
16413            false,
16414            1,
16415            window,
16416            cx,
16417        );
16418        assert_eq!(
16419            editor.selections.ranges(&editor.display_snapshot(cx)),
16420            [Point::new(1, 3)..Point::new(1, 3)]
16421        );
16422        editor
16423    });
16424
16425    multibuffer.update(cx, |multibuffer, cx| {
16426        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16427    });
16428    _ = editor.update(cx, |editor, window, cx| {
16429        assert_eq!(
16430            editor.selections.ranges(&editor.display_snapshot(cx)),
16431            [Point::new(0, 0)..Point::new(0, 0)]
16432        );
16433
16434        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
16435        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16436        assert_eq!(
16437            editor.selections.ranges(&editor.display_snapshot(cx)),
16438            [Point::new(0, 3)..Point::new(0, 3)]
16439        );
16440        assert!(editor.selections.pending_anchor().is_some());
16441    });
16442}
16443
16444#[gpui::test]
16445async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
16446    init_test(cx, |_| {});
16447
16448    let language = Arc::new(
16449        Language::new(
16450            LanguageConfig {
16451                brackets: BracketPairConfig {
16452                    pairs: vec![
16453                        BracketPair {
16454                            start: "{".to_string(),
16455                            end: "}".to_string(),
16456                            close: true,
16457                            surround: true,
16458                            newline: true,
16459                        },
16460                        BracketPair {
16461                            start: "/* ".to_string(),
16462                            end: " */".to_string(),
16463                            close: true,
16464                            surround: true,
16465                            newline: true,
16466                        },
16467                    ],
16468                    ..Default::default()
16469                },
16470                ..Default::default()
16471            },
16472            Some(tree_sitter_rust::LANGUAGE.into()),
16473        )
16474        .with_indents_query("")
16475        .unwrap(),
16476    );
16477
16478    let text = concat!(
16479        "{   }\n",     //
16480        "  x\n",       //
16481        "  /*   */\n", //
16482        "x\n",         //
16483        "{{} }\n",     //
16484    );
16485
16486    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
16487    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16488    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16489    editor
16490        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
16491        .await;
16492
16493    editor.update_in(cx, |editor, window, cx| {
16494        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16495            s.select_display_ranges([
16496                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
16497                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
16498                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
16499            ])
16500        });
16501        editor.newline(&Newline, window, cx);
16502
16503        assert_eq!(
16504            editor.buffer().read(cx).read(cx).text(),
16505            concat!(
16506                "{ \n",    // Suppress rustfmt
16507                "\n",      //
16508                "}\n",     //
16509                "  x\n",   //
16510                "  /* \n", //
16511                "  \n",    //
16512                "  */\n",  //
16513                "x\n",     //
16514                "{{} \n",  //
16515                "}\n",     //
16516            )
16517        );
16518    });
16519}
16520
16521#[gpui::test]
16522fn test_highlighted_ranges(cx: &mut TestAppContext) {
16523    init_test(cx, |_| {});
16524
16525    let editor = cx.add_window(|window, cx| {
16526        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
16527        build_editor(buffer, window, cx)
16528    });
16529
16530    _ = editor.update(cx, |editor, window, cx| {
16531        struct Type1;
16532        struct Type2;
16533
16534        let buffer = editor.buffer.read(cx).snapshot(cx);
16535
16536        let anchor_range =
16537            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
16538
16539        editor.highlight_background::<Type1>(
16540            &[
16541                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
16542                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
16543                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
16544                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
16545            ],
16546            |_| Hsla::red(),
16547            cx,
16548        );
16549        editor.highlight_background::<Type2>(
16550            &[
16551                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
16552                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
16553                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
16554                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
16555            ],
16556            |_| Hsla::green(),
16557            cx,
16558        );
16559
16560        let snapshot = editor.snapshot(window, cx);
16561        let highlighted_ranges = editor.sorted_background_highlights_in_range(
16562            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
16563            &snapshot,
16564            cx.theme(),
16565        );
16566        assert_eq!(
16567            highlighted_ranges,
16568            &[
16569                (
16570                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
16571                    Hsla::green(),
16572                ),
16573                (
16574                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
16575                    Hsla::red(),
16576                ),
16577                (
16578                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
16579                    Hsla::green(),
16580                ),
16581                (
16582                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16583                    Hsla::red(),
16584                ),
16585            ]
16586        );
16587        assert_eq!(
16588            editor.sorted_background_highlights_in_range(
16589                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
16590                &snapshot,
16591                cx.theme(),
16592            ),
16593            &[(
16594                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16595                Hsla::red(),
16596            )]
16597        );
16598    });
16599}
16600
16601#[gpui::test]
16602async fn test_following(cx: &mut TestAppContext) {
16603    init_test(cx, |_| {});
16604
16605    let fs = FakeFs::new(cx.executor());
16606    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16607
16608    let buffer = project.update(cx, |project, cx| {
16609        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
16610        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
16611    });
16612    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
16613    let follower = cx.update(|cx| {
16614        cx.open_window(
16615            WindowOptions {
16616                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
16617                    gpui::Point::new(px(0.), px(0.)),
16618                    gpui::Point::new(px(10.), px(80.)),
16619                ))),
16620                ..Default::default()
16621            },
16622            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
16623        )
16624        .unwrap()
16625    });
16626
16627    let is_still_following = Rc::new(RefCell::new(true));
16628    let follower_edit_event_count = Rc::new(RefCell::new(0));
16629    let pending_update = Rc::new(RefCell::new(None));
16630    let leader_entity = leader.root(cx).unwrap();
16631    let follower_entity = follower.root(cx).unwrap();
16632    _ = follower.update(cx, {
16633        let update = pending_update.clone();
16634        let is_still_following = is_still_following.clone();
16635        let follower_edit_event_count = follower_edit_event_count.clone();
16636        |_, window, cx| {
16637            cx.subscribe_in(
16638                &leader_entity,
16639                window,
16640                move |_, leader, event, window, cx| {
16641                    leader.read(cx).add_event_to_update_proto(
16642                        event,
16643                        &mut update.borrow_mut(),
16644                        window,
16645                        cx,
16646                    );
16647                },
16648            )
16649            .detach();
16650
16651            cx.subscribe_in(
16652                &follower_entity,
16653                window,
16654                move |_, _, event: &EditorEvent, _window, _cx| {
16655                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
16656                        *is_still_following.borrow_mut() = false;
16657                    }
16658
16659                    if let EditorEvent::BufferEdited = event {
16660                        *follower_edit_event_count.borrow_mut() += 1;
16661                    }
16662                },
16663            )
16664            .detach();
16665        }
16666    });
16667
16668    // Update the selections only
16669    _ = leader.update(cx, |leader, window, cx| {
16670        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16671            s.select_ranges([1..1])
16672        });
16673    });
16674    follower
16675        .update(cx, |follower, window, cx| {
16676            follower.apply_update_proto(
16677                &project,
16678                pending_update.borrow_mut().take().unwrap(),
16679                window,
16680                cx,
16681            )
16682        })
16683        .unwrap()
16684        .await
16685        .unwrap();
16686    _ = follower.update(cx, |follower, _, cx| {
16687        assert_eq!(
16688            follower.selections.ranges(&follower.display_snapshot(cx)),
16689            vec![1..1]
16690        );
16691    });
16692    assert!(*is_still_following.borrow());
16693    assert_eq!(*follower_edit_event_count.borrow(), 0);
16694
16695    // Update the scroll position only
16696    _ = leader.update(cx, |leader, window, cx| {
16697        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16698    });
16699    follower
16700        .update(cx, |follower, window, cx| {
16701            follower.apply_update_proto(
16702                &project,
16703                pending_update.borrow_mut().take().unwrap(),
16704                window,
16705                cx,
16706            )
16707        })
16708        .unwrap()
16709        .await
16710        .unwrap();
16711    assert_eq!(
16712        follower
16713            .update(cx, |follower, _, cx| follower.scroll_position(cx))
16714            .unwrap(),
16715        gpui::Point::new(1.5, 3.5)
16716    );
16717    assert!(*is_still_following.borrow());
16718    assert_eq!(*follower_edit_event_count.borrow(), 0);
16719
16720    // Update the selections and scroll position. The follower's scroll position is updated
16721    // via autoscroll, not via the leader's exact scroll position.
16722    _ = leader.update(cx, |leader, window, cx| {
16723        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16724            s.select_ranges([0..0])
16725        });
16726        leader.request_autoscroll(Autoscroll::newest(), cx);
16727        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16728    });
16729    follower
16730        .update(cx, |follower, window, cx| {
16731            follower.apply_update_proto(
16732                &project,
16733                pending_update.borrow_mut().take().unwrap(),
16734                window,
16735                cx,
16736            )
16737        })
16738        .unwrap()
16739        .await
16740        .unwrap();
16741    _ = follower.update(cx, |follower, _, cx| {
16742        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
16743        assert_eq!(
16744            follower.selections.ranges(&follower.display_snapshot(cx)),
16745            vec![0..0]
16746        );
16747    });
16748    assert!(*is_still_following.borrow());
16749
16750    // Creating a pending selection that precedes another selection
16751    _ = leader.update(cx, |leader, window, cx| {
16752        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16753            s.select_ranges([1..1])
16754        });
16755        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
16756    });
16757    follower
16758        .update(cx, |follower, window, cx| {
16759            follower.apply_update_proto(
16760                &project,
16761                pending_update.borrow_mut().take().unwrap(),
16762                window,
16763                cx,
16764            )
16765        })
16766        .unwrap()
16767        .await
16768        .unwrap();
16769    _ = follower.update(cx, |follower, _, cx| {
16770        assert_eq!(
16771            follower.selections.ranges(&follower.display_snapshot(cx)),
16772            vec![0..0, 1..1]
16773        );
16774    });
16775    assert!(*is_still_following.borrow());
16776
16777    // Extend the pending selection so that it surrounds another selection
16778    _ = leader.update(cx, |leader, window, cx| {
16779        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
16780    });
16781    follower
16782        .update(cx, |follower, window, cx| {
16783            follower.apply_update_proto(
16784                &project,
16785                pending_update.borrow_mut().take().unwrap(),
16786                window,
16787                cx,
16788            )
16789        })
16790        .unwrap()
16791        .await
16792        .unwrap();
16793    _ = follower.update(cx, |follower, _, cx| {
16794        assert_eq!(
16795            follower.selections.ranges(&follower.display_snapshot(cx)),
16796            vec![0..2]
16797        );
16798    });
16799
16800    // Scrolling locally breaks the follow
16801    _ = follower.update(cx, |follower, window, cx| {
16802        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
16803        follower.set_scroll_anchor(
16804            ScrollAnchor {
16805                anchor: top_anchor,
16806                offset: gpui::Point::new(0.0, 0.5),
16807            },
16808            window,
16809            cx,
16810        );
16811    });
16812    assert!(!(*is_still_following.borrow()));
16813}
16814
16815#[gpui::test]
16816async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
16817    init_test(cx, |_| {});
16818
16819    let fs = FakeFs::new(cx.executor());
16820    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16821    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16822    let pane = workspace
16823        .update(cx, |workspace, _, _| workspace.active_pane().clone())
16824        .unwrap();
16825
16826    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16827
16828    let leader = pane.update_in(cx, |_, window, cx| {
16829        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
16830        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
16831    });
16832
16833    // Start following the editor when it has no excerpts.
16834    let mut state_message =
16835        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16836    let workspace_entity = workspace.root(cx).unwrap();
16837    let follower_1 = cx
16838        .update_window(*workspace.deref(), |_, window, cx| {
16839            Editor::from_state_proto(
16840                workspace_entity,
16841                ViewId {
16842                    creator: CollaboratorId::PeerId(PeerId::default()),
16843                    id: 0,
16844                },
16845                &mut state_message,
16846                window,
16847                cx,
16848            )
16849        })
16850        .unwrap()
16851        .unwrap()
16852        .await
16853        .unwrap();
16854
16855    let update_message = Rc::new(RefCell::new(None));
16856    follower_1.update_in(cx, {
16857        let update = update_message.clone();
16858        |_, window, cx| {
16859            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
16860                leader.read(cx).add_event_to_update_proto(
16861                    event,
16862                    &mut update.borrow_mut(),
16863                    window,
16864                    cx,
16865                );
16866            })
16867            .detach();
16868        }
16869    });
16870
16871    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
16872        (
16873            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
16874            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
16875        )
16876    });
16877
16878    // Insert some excerpts.
16879    leader.update(cx, |leader, cx| {
16880        leader.buffer.update(cx, |multibuffer, cx| {
16881            multibuffer.set_excerpts_for_path(
16882                PathKey::with_sort_prefix(1, rel_path("b.txt").into_arc()),
16883                buffer_1.clone(),
16884                vec![
16885                    Point::row_range(0..3),
16886                    Point::row_range(1..6),
16887                    Point::row_range(12..15),
16888                ],
16889                0,
16890                cx,
16891            );
16892            multibuffer.set_excerpts_for_path(
16893                PathKey::with_sort_prefix(1, rel_path("a.txt").into_arc()),
16894                buffer_2.clone(),
16895                vec![Point::row_range(0..6), Point::row_range(8..12)],
16896                0,
16897                cx,
16898            );
16899        });
16900    });
16901
16902    // Apply the update of adding the excerpts.
16903    follower_1
16904        .update_in(cx, |follower, window, cx| {
16905            follower.apply_update_proto(
16906                &project,
16907                update_message.borrow().clone().unwrap(),
16908                window,
16909                cx,
16910            )
16911        })
16912        .await
16913        .unwrap();
16914    assert_eq!(
16915        follower_1.update(cx, |editor, cx| editor.text(cx)),
16916        leader.update(cx, |editor, cx| editor.text(cx))
16917    );
16918    update_message.borrow_mut().take();
16919
16920    // Start following separately after it already has excerpts.
16921    let mut state_message =
16922        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16923    let workspace_entity = workspace.root(cx).unwrap();
16924    let follower_2 = cx
16925        .update_window(*workspace.deref(), |_, window, cx| {
16926            Editor::from_state_proto(
16927                workspace_entity,
16928                ViewId {
16929                    creator: CollaboratorId::PeerId(PeerId::default()),
16930                    id: 0,
16931                },
16932                &mut state_message,
16933                window,
16934                cx,
16935            )
16936        })
16937        .unwrap()
16938        .unwrap()
16939        .await
16940        .unwrap();
16941    assert_eq!(
16942        follower_2.update(cx, |editor, cx| editor.text(cx)),
16943        leader.update(cx, |editor, cx| editor.text(cx))
16944    );
16945
16946    // Remove some excerpts.
16947    leader.update(cx, |leader, cx| {
16948        leader.buffer.update(cx, |multibuffer, cx| {
16949            let excerpt_ids = multibuffer.excerpt_ids();
16950            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
16951            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
16952        });
16953    });
16954
16955    // Apply the update of removing the excerpts.
16956    follower_1
16957        .update_in(cx, |follower, window, cx| {
16958            follower.apply_update_proto(
16959                &project,
16960                update_message.borrow().clone().unwrap(),
16961                window,
16962                cx,
16963            )
16964        })
16965        .await
16966        .unwrap();
16967    follower_2
16968        .update_in(cx, |follower, window, cx| {
16969            follower.apply_update_proto(
16970                &project,
16971                update_message.borrow().clone().unwrap(),
16972                window,
16973                cx,
16974            )
16975        })
16976        .await
16977        .unwrap();
16978    update_message.borrow_mut().take();
16979    assert_eq!(
16980        follower_1.update(cx, |editor, cx| editor.text(cx)),
16981        leader.update(cx, |editor, cx| editor.text(cx))
16982    );
16983}
16984
16985#[gpui::test]
16986async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16987    init_test(cx, |_| {});
16988
16989    let mut cx = EditorTestContext::new(cx).await;
16990    let lsp_store =
16991        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
16992
16993    cx.set_state(indoc! {"
16994        ˇfn func(abc def: i32) -> u32 {
16995        }
16996    "});
16997
16998    cx.update(|_, cx| {
16999        lsp_store.update(cx, |lsp_store, cx| {
17000            lsp_store
17001                .update_diagnostics(
17002                    LanguageServerId(0),
17003                    lsp::PublishDiagnosticsParams {
17004                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
17005                        version: None,
17006                        diagnostics: vec![
17007                            lsp::Diagnostic {
17008                                range: lsp::Range::new(
17009                                    lsp::Position::new(0, 11),
17010                                    lsp::Position::new(0, 12),
17011                                ),
17012                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17013                                ..Default::default()
17014                            },
17015                            lsp::Diagnostic {
17016                                range: lsp::Range::new(
17017                                    lsp::Position::new(0, 12),
17018                                    lsp::Position::new(0, 15),
17019                                ),
17020                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17021                                ..Default::default()
17022                            },
17023                            lsp::Diagnostic {
17024                                range: lsp::Range::new(
17025                                    lsp::Position::new(0, 25),
17026                                    lsp::Position::new(0, 28),
17027                                ),
17028                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17029                                ..Default::default()
17030                            },
17031                        ],
17032                    },
17033                    None,
17034                    DiagnosticSourceKind::Pushed,
17035                    &[],
17036                    cx,
17037                )
17038                .unwrap()
17039        });
17040    });
17041
17042    executor.run_until_parked();
17043
17044    cx.update_editor(|editor, window, cx| {
17045        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17046    });
17047
17048    cx.assert_editor_state(indoc! {"
17049        fn func(abc def: i32) -> ˇu32 {
17050        }
17051    "});
17052
17053    cx.update_editor(|editor, window, cx| {
17054        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17055    });
17056
17057    cx.assert_editor_state(indoc! {"
17058        fn func(abc ˇdef: i32) -> u32 {
17059        }
17060    "});
17061
17062    cx.update_editor(|editor, window, cx| {
17063        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17064    });
17065
17066    cx.assert_editor_state(indoc! {"
17067        fn func(abcˇ def: i32) -> u32 {
17068        }
17069    "});
17070
17071    cx.update_editor(|editor, window, cx| {
17072        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17073    });
17074
17075    cx.assert_editor_state(indoc! {"
17076        fn func(abc def: i32) -> ˇu32 {
17077        }
17078    "});
17079}
17080
17081#[gpui::test]
17082async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17083    init_test(cx, |_| {});
17084
17085    let mut cx = EditorTestContext::new(cx).await;
17086
17087    let diff_base = r#"
17088        use some::mod;
17089
17090        const A: u32 = 42;
17091
17092        fn main() {
17093            println!("hello");
17094
17095            println!("world");
17096        }
17097        "#
17098    .unindent();
17099
17100    // Edits are modified, removed, modified, added
17101    cx.set_state(
17102        &r#"
17103        use some::modified;
17104
17105        ˇ
17106        fn main() {
17107            println!("hello there");
17108
17109            println!("around the");
17110            println!("world");
17111        }
17112        "#
17113        .unindent(),
17114    );
17115
17116    cx.set_head_text(&diff_base);
17117    executor.run_until_parked();
17118
17119    cx.update_editor(|editor, window, cx| {
17120        //Wrap around the bottom of the buffer
17121        for _ in 0..3 {
17122            editor.go_to_next_hunk(&GoToHunk, window, cx);
17123        }
17124    });
17125
17126    cx.assert_editor_state(
17127        &r#"
17128        ˇuse some::modified;
17129
17130
17131        fn main() {
17132            println!("hello there");
17133
17134            println!("around the");
17135            println!("world");
17136        }
17137        "#
17138        .unindent(),
17139    );
17140
17141    cx.update_editor(|editor, window, cx| {
17142        //Wrap around the top of the buffer
17143        for _ in 0..2 {
17144            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17145        }
17146    });
17147
17148    cx.assert_editor_state(
17149        &r#"
17150        use some::modified;
17151
17152
17153        fn main() {
17154        ˇ    println!("hello there");
17155
17156            println!("around the");
17157            println!("world");
17158        }
17159        "#
17160        .unindent(),
17161    );
17162
17163    cx.update_editor(|editor, window, cx| {
17164        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17165    });
17166
17167    cx.assert_editor_state(
17168        &r#"
17169        use some::modified;
17170
17171        ˇ
17172        fn main() {
17173            println!("hello there");
17174
17175            println!("around the");
17176            println!("world");
17177        }
17178        "#
17179        .unindent(),
17180    );
17181
17182    cx.update_editor(|editor, window, cx| {
17183        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17184    });
17185
17186    cx.assert_editor_state(
17187        &r#"
17188        ˇuse some::modified;
17189
17190
17191        fn main() {
17192            println!("hello there");
17193
17194            println!("around the");
17195            println!("world");
17196        }
17197        "#
17198        .unindent(),
17199    );
17200
17201    cx.update_editor(|editor, window, cx| {
17202        for _ in 0..2 {
17203            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17204        }
17205    });
17206
17207    cx.assert_editor_state(
17208        &r#"
17209        use some::modified;
17210
17211
17212        fn main() {
17213        ˇ    println!("hello there");
17214
17215            println!("around the");
17216            println!("world");
17217        }
17218        "#
17219        .unindent(),
17220    );
17221
17222    cx.update_editor(|editor, window, cx| {
17223        editor.fold(&Fold, window, cx);
17224    });
17225
17226    cx.update_editor(|editor, window, cx| {
17227        editor.go_to_next_hunk(&GoToHunk, window, cx);
17228    });
17229
17230    cx.assert_editor_state(
17231        &r#"
17232        ˇuse some::modified;
17233
17234
17235        fn main() {
17236            println!("hello there");
17237
17238            println!("around the");
17239            println!("world");
17240        }
17241        "#
17242        .unindent(),
17243    );
17244}
17245
17246#[test]
17247fn test_split_words() {
17248    fn split(text: &str) -> Vec<&str> {
17249        split_words(text).collect()
17250    }
17251
17252    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
17253    assert_eq!(split("hello_world"), &["hello_", "world"]);
17254    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
17255    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
17256    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
17257    assert_eq!(split("helloworld"), &["helloworld"]);
17258
17259    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
17260}
17261
17262#[gpui::test]
17263async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
17264    init_test(cx, |_| {});
17265
17266    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
17267    let mut assert = |before, after| {
17268        let _state_context = cx.set_state(before);
17269        cx.run_until_parked();
17270        cx.update_editor(|editor, window, cx| {
17271            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
17272        });
17273        cx.run_until_parked();
17274        cx.assert_editor_state(after);
17275    };
17276
17277    // Outside bracket jumps to outside of matching bracket
17278    assert("console.logˇ(var);", "console.log(var)ˇ;");
17279    assert("console.log(var)ˇ;", "console.logˇ(var);");
17280
17281    // Inside bracket jumps to inside of matching bracket
17282    assert("console.log(ˇvar);", "console.log(varˇ);");
17283    assert("console.log(varˇ);", "console.log(ˇvar);");
17284
17285    // When outside a bracket and inside, favor jumping to the inside bracket
17286    assert(
17287        "console.log('foo', [1, 2, 3]ˇ);",
17288        "console.log(ˇ'foo', [1, 2, 3]);",
17289    );
17290    assert(
17291        "console.log(ˇ'foo', [1, 2, 3]);",
17292        "console.log('foo', [1, 2, 3]ˇ);",
17293    );
17294
17295    // Bias forward if two options are equally likely
17296    assert(
17297        "let result = curried_fun()ˇ();",
17298        "let result = curried_fun()()ˇ;",
17299    );
17300
17301    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
17302    assert(
17303        indoc! {"
17304            function test() {
17305                console.log('test')ˇ
17306            }"},
17307        indoc! {"
17308            function test() {
17309                console.logˇ('test')
17310            }"},
17311    );
17312}
17313
17314#[gpui::test]
17315async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
17316    init_test(cx, |_| {});
17317
17318    let fs = FakeFs::new(cx.executor());
17319    fs.insert_tree(
17320        path!("/a"),
17321        json!({
17322            "main.rs": "fn main() { let a = 5; }",
17323            "other.rs": "// Test file",
17324        }),
17325    )
17326    .await;
17327    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17328
17329    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17330    language_registry.add(Arc::new(Language::new(
17331        LanguageConfig {
17332            name: "Rust".into(),
17333            matcher: LanguageMatcher {
17334                path_suffixes: vec!["rs".to_string()],
17335                ..Default::default()
17336            },
17337            brackets: BracketPairConfig {
17338                pairs: vec![BracketPair {
17339                    start: "{".to_string(),
17340                    end: "}".to_string(),
17341                    close: true,
17342                    surround: true,
17343                    newline: true,
17344                }],
17345                disabled_scopes_by_bracket_ix: Vec::new(),
17346            },
17347            ..Default::default()
17348        },
17349        Some(tree_sitter_rust::LANGUAGE.into()),
17350    )));
17351    let mut fake_servers = language_registry.register_fake_lsp(
17352        "Rust",
17353        FakeLspAdapter {
17354            capabilities: lsp::ServerCapabilities {
17355                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17356                    first_trigger_character: "{".to_string(),
17357                    more_trigger_character: None,
17358                }),
17359                ..Default::default()
17360            },
17361            ..Default::default()
17362        },
17363    );
17364
17365    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17366
17367    let cx = &mut VisualTestContext::from_window(*workspace, cx);
17368
17369    let worktree_id = workspace
17370        .update(cx, |workspace, _, cx| {
17371            workspace.project().update(cx, |project, cx| {
17372                project.worktrees(cx).next().unwrap().read(cx).id()
17373            })
17374        })
17375        .unwrap();
17376
17377    let buffer = project
17378        .update(cx, |project, cx| {
17379            project.open_local_buffer(path!("/a/main.rs"), cx)
17380        })
17381        .await
17382        .unwrap();
17383    let editor_handle = workspace
17384        .update(cx, |workspace, window, cx| {
17385            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
17386        })
17387        .unwrap()
17388        .await
17389        .unwrap()
17390        .downcast::<Editor>()
17391        .unwrap();
17392
17393    cx.executor().start_waiting();
17394    let fake_server = fake_servers.next().await.unwrap();
17395
17396    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
17397        |params, _| async move {
17398            assert_eq!(
17399                params.text_document_position.text_document.uri,
17400                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
17401            );
17402            assert_eq!(
17403                params.text_document_position.position,
17404                lsp::Position::new(0, 21),
17405            );
17406
17407            Ok(Some(vec![lsp::TextEdit {
17408                new_text: "]".to_string(),
17409                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17410            }]))
17411        },
17412    );
17413
17414    editor_handle.update_in(cx, |editor, window, cx| {
17415        window.focus(&editor.focus_handle(cx));
17416        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17417            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
17418        });
17419        editor.handle_input("{", window, cx);
17420    });
17421
17422    cx.executor().run_until_parked();
17423
17424    buffer.update(cx, |buffer, _| {
17425        assert_eq!(
17426            buffer.text(),
17427            "fn main() { let a = {5}; }",
17428            "No extra braces from on type formatting should appear in the buffer"
17429        )
17430    });
17431}
17432
17433#[gpui::test(iterations = 20, seeds(31))]
17434async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
17435    init_test(cx, |_| {});
17436
17437    let mut cx = EditorLspTestContext::new_rust(
17438        lsp::ServerCapabilities {
17439            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17440                first_trigger_character: ".".to_string(),
17441                more_trigger_character: None,
17442            }),
17443            ..Default::default()
17444        },
17445        cx,
17446    )
17447    .await;
17448
17449    cx.update_buffer(|buffer, _| {
17450        // This causes autoindent to be async.
17451        buffer.set_sync_parse_timeout(Duration::ZERO)
17452    });
17453
17454    cx.set_state("fn c() {\n    d()ˇ\n}\n");
17455    cx.simulate_keystroke("\n");
17456    cx.run_until_parked();
17457
17458    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
17459    let mut request =
17460        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
17461            let buffer_cloned = buffer_cloned.clone();
17462            async move {
17463                buffer_cloned.update(&mut cx, |buffer, _| {
17464                    assert_eq!(
17465                        buffer.text(),
17466                        "fn c() {\n    d()\n        .\n}\n",
17467                        "OnTypeFormatting should triggered after autoindent applied"
17468                    )
17469                })?;
17470
17471                Ok(Some(vec![]))
17472            }
17473        });
17474
17475    cx.simulate_keystroke(".");
17476    cx.run_until_parked();
17477
17478    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
17479    assert!(request.next().await.is_some());
17480    request.close();
17481    assert!(request.next().await.is_none());
17482}
17483
17484#[gpui::test]
17485async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
17486    init_test(cx, |_| {});
17487
17488    let fs = FakeFs::new(cx.executor());
17489    fs.insert_tree(
17490        path!("/a"),
17491        json!({
17492            "main.rs": "fn main() { let a = 5; }",
17493            "other.rs": "// Test file",
17494        }),
17495    )
17496    .await;
17497
17498    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17499
17500    let server_restarts = Arc::new(AtomicUsize::new(0));
17501    let closure_restarts = Arc::clone(&server_restarts);
17502    let language_server_name = "test language server";
17503    let language_name: LanguageName = "Rust".into();
17504
17505    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17506    language_registry.add(Arc::new(Language::new(
17507        LanguageConfig {
17508            name: language_name.clone(),
17509            matcher: LanguageMatcher {
17510                path_suffixes: vec!["rs".to_string()],
17511                ..Default::default()
17512            },
17513            ..Default::default()
17514        },
17515        Some(tree_sitter_rust::LANGUAGE.into()),
17516    )));
17517    let mut fake_servers = language_registry.register_fake_lsp(
17518        "Rust",
17519        FakeLspAdapter {
17520            name: language_server_name,
17521            initialization_options: Some(json!({
17522                "testOptionValue": true
17523            })),
17524            initializer: Some(Box::new(move |fake_server| {
17525                let task_restarts = Arc::clone(&closure_restarts);
17526                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
17527                    task_restarts.fetch_add(1, atomic::Ordering::Release);
17528                    futures::future::ready(Ok(()))
17529                });
17530            })),
17531            ..Default::default()
17532        },
17533    );
17534
17535    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17536    let _buffer = project
17537        .update(cx, |project, cx| {
17538            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
17539        })
17540        .await
17541        .unwrap();
17542    let _fake_server = fake_servers.next().await.unwrap();
17543    update_test_language_settings(cx, |language_settings| {
17544        language_settings.languages.0.insert(
17545            language_name.clone().0,
17546            LanguageSettingsContent {
17547                tab_size: NonZeroU32::new(8),
17548                ..Default::default()
17549            },
17550        );
17551    });
17552    cx.executor().run_until_parked();
17553    assert_eq!(
17554        server_restarts.load(atomic::Ordering::Acquire),
17555        0,
17556        "Should not restart LSP server on an unrelated change"
17557    );
17558
17559    update_test_project_settings(cx, |project_settings| {
17560        project_settings.lsp.insert(
17561            "Some other server name".into(),
17562            LspSettings {
17563                binary: None,
17564                settings: None,
17565                initialization_options: Some(json!({
17566                    "some other init value": false
17567                })),
17568                enable_lsp_tasks: false,
17569                fetch: None,
17570            },
17571        );
17572    });
17573    cx.executor().run_until_parked();
17574    assert_eq!(
17575        server_restarts.load(atomic::Ordering::Acquire),
17576        0,
17577        "Should not restart LSP server on an unrelated LSP settings change"
17578    );
17579
17580    update_test_project_settings(cx, |project_settings| {
17581        project_settings.lsp.insert(
17582            language_server_name.into(),
17583            LspSettings {
17584                binary: None,
17585                settings: None,
17586                initialization_options: Some(json!({
17587                    "anotherInitValue": false
17588                })),
17589                enable_lsp_tasks: false,
17590                fetch: None,
17591            },
17592        );
17593    });
17594    cx.executor().run_until_parked();
17595    assert_eq!(
17596        server_restarts.load(atomic::Ordering::Acquire),
17597        1,
17598        "Should restart LSP server on a related LSP settings change"
17599    );
17600
17601    update_test_project_settings(cx, |project_settings| {
17602        project_settings.lsp.insert(
17603            language_server_name.into(),
17604            LspSettings {
17605                binary: None,
17606                settings: None,
17607                initialization_options: Some(json!({
17608                    "anotherInitValue": false
17609                })),
17610                enable_lsp_tasks: false,
17611                fetch: None,
17612            },
17613        );
17614    });
17615    cx.executor().run_until_parked();
17616    assert_eq!(
17617        server_restarts.load(atomic::Ordering::Acquire),
17618        1,
17619        "Should not restart LSP server on a related LSP settings change that is the same"
17620    );
17621
17622    update_test_project_settings(cx, |project_settings| {
17623        project_settings.lsp.insert(
17624            language_server_name.into(),
17625            LspSettings {
17626                binary: None,
17627                settings: None,
17628                initialization_options: None,
17629                enable_lsp_tasks: false,
17630                fetch: None,
17631            },
17632        );
17633    });
17634    cx.executor().run_until_parked();
17635    assert_eq!(
17636        server_restarts.load(atomic::Ordering::Acquire),
17637        2,
17638        "Should restart LSP server on another related LSP settings change"
17639    );
17640}
17641
17642#[gpui::test]
17643async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
17644    init_test(cx, |_| {});
17645
17646    let mut cx = EditorLspTestContext::new_rust(
17647        lsp::ServerCapabilities {
17648            completion_provider: Some(lsp::CompletionOptions {
17649                trigger_characters: Some(vec![".".to_string()]),
17650                resolve_provider: Some(true),
17651                ..Default::default()
17652            }),
17653            ..Default::default()
17654        },
17655        cx,
17656    )
17657    .await;
17658
17659    cx.set_state("fn main() { let a = 2ˇ; }");
17660    cx.simulate_keystroke(".");
17661    let completion_item = lsp::CompletionItem {
17662        label: "some".into(),
17663        kind: Some(lsp::CompletionItemKind::SNIPPET),
17664        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
17665        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
17666            kind: lsp::MarkupKind::Markdown,
17667            value: "```rust\nSome(2)\n```".to_string(),
17668        })),
17669        deprecated: Some(false),
17670        sort_text: Some("fffffff2".to_string()),
17671        filter_text: Some("some".to_string()),
17672        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
17673        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17674            range: lsp::Range {
17675                start: lsp::Position {
17676                    line: 0,
17677                    character: 22,
17678                },
17679                end: lsp::Position {
17680                    line: 0,
17681                    character: 22,
17682                },
17683            },
17684            new_text: "Some(2)".to_string(),
17685        })),
17686        additional_text_edits: Some(vec![lsp::TextEdit {
17687            range: lsp::Range {
17688                start: lsp::Position {
17689                    line: 0,
17690                    character: 20,
17691                },
17692                end: lsp::Position {
17693                    line: 0,
17694                    character: 22,
17695                },
17696            },
17697            new_text: "".to_string(),
17698        }]),
17699        ..Default::default()
17700    };
17701
17702    let closure_completion_item = completion_item.clone();
17703    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17704        let task_completion_item = closure_completion_item.clone();
17705        async move {
17706            Ok(Some(lsp::CompletionResponse::Array(vec![
17707                task_completion_item,
17708            ])))
17709        }
17710    });
17711
17712    request.next().await;
17713
17714    cx.condition(|editor, _| editor.context_menu_visible())
17715        .await;
17716    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
17717        editor
17718            .confirm_completion(&ConfirmCompletion::default(), window, cx)
17719            .unwrap()
17720    });
17721    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
17722
17723    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
17724        let task_completion_item = completion_item.clone();
17725        async move { Ok(task_completion_item) }
17726    })
17727    .next()
17728    .await
17729    .unwrap();
17730    apply_additional_edits.await.unwrap();
17731    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
17732}
17733
17734#[gpui::test]
17735async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
17736    init_test(cx, |_| {});
17737
17738    let mut cx = EditorLspTestContext::new_rust(
17739        lsp::ServerCapabilities {
17740            completion_provider: Some(lsp::CompletionOptions {
17741                trigger_characters: Some(vec![".".to_string()]),
17742                resolve_provider: Some(true),
17743                ..Default::default()
17744            }),
17745            ..Default::default()
17746        },
17747        cx,
17748    )
17749    .await;
17750
17751    cx.set_state("fn main() { let a = 2ˇ; }");
17752    cx.simulate_keystroke(".");
17753
17754    let item1 = lsp::CompletionItem {
17755        label: "method id()".to_string(),
17756        filter_text: Some("id".to_string()),
17757        detail: None,
17758        documentation: None,
17759        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17760            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17761            new_text: ".id".to_string(),
17762        })),
17763        ..lsp::CompletionItem::default()
17764    };
17765
17766    let item2 = lsp::CompletionItem {
17767        label: "other".to_string(),
17768        filter_text: Some("other".to_string()),
17769        detail: None,
17770        documentation: None,
17771        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17772            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17773            new_text: ".other".to_string(),
17774        })),
17775        ..lsp::CompletionItem::default()
17776    };
17777
17778    let item1 = item1.clone();
17779    cx.set_request_handler::<lsp::request::Completion, _, _>({
17780        let item1 = item1.clone();
17781        move |_, _, _| {
17782            let item1 = item1.clone();
17783            let item2 = item2.clone();
17784            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
17785        }
17786    })
17787    .next()
17788    .await;
17789
17790    cx.condition(|editor, _| editor.context_menu_visible())
17791        .await;
17792    cx.update_editor(|editor, _, _| {
17793        let context_menu = editor.context_menu.borrow_mut();
17794        let context_menu = context_menu
17795            .as_ref()
17796            .expect("Should have the context menu deployed");
17797        match context_menu {
17798            CodeContextMenu::Completions(completions_menu) => {
17799                let completions = completions_menu.completions.borrow_mut();
17800                assert_eq!(
17801                    completions
17802                        .iter()
17803                        .map(|completion| &completion.label.text)
17804                        .collect::<Vec<_>>(),
17805                    vec!["method id()", "other"]
17806                )
17807            }
17808            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17809        }
17810    });
17811
17812    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
17813        let item1 = item1.clone();
17814        move |_, item_to_resolve, _| {
17815            let item1 = item1.clone();
17816            async move {
17817                if item1 == item_to_resolve {
17818                    Ok(lsp::CompletionItem {
17819                        label: "method id()".to_string(),
17820                        filter_text: Some("id".to_string()),
17821                        detail: Some("Now resolved!".to_string()),
17822                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
17823                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17824                            range: lsp::Range::new(
17825                                lsp::Position::new(0, 22),
17826                                lsp::Position::new(0, 22),
17827                            ),
17828                            new_text: ".id".to_string(),
17829                        })),
17830                        ..lsp::CompletionItem::default()
17831                    })
17832                } else {
17833                    Ok(item_to_resolve)
17834                }
17835            }
17836        }
17837    })
17838    .next()
17839    .await
17840    .unwrap();
17841    cx.run_until_parked();
17842
17843    cx.update_editor(|editor, window, cx| {
17844        editor.context_menu_next(&Default::default(), window, cx);
17845    });
17846
17847    cx.update_editor(|editor, _, _| {
17848        let context_menu = editor.context_menu.borrow_mut();
17849        let context_menu = context_menu
17850            .as_ref()
17851            .expect("Should have the context menu deployed");
17852        match context_menu {
17853            CodeContextMenu::Completions(completions_menu) => {
17854                let completions = completions_menu.completions.borrow_mut();
17855                assert_eq!(
17856                    completions
17857                        .iter()
17858                        .map(|completion| &completion.label.text)
17859                        .collect::<Vec<_>>(),
17860                    vec!["method id() Now resolved!", "other"],
17861                    "Should update first completion label, but not second as the filter text did not match."
17862                );
17863            }
17864            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17865        }
17866    });
17867}
17868
17869#[gpui::test]
17870async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
17871    init_test(cx, |_| {});
17872    let mut cx = EditorLspTestContext::new_rust(
17873        lsp::ServerCapabilities {
17874            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
17875            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
17876            completion_provider: Some(lsp::CompletionOptions {
17877                resolve_provider: Some(true),
17878                ..Default::default()
17879            }),
17880            ..Default::default()
17881        },
17882        cx,
17883    )
17884    .await;
17885    cx.set_state(indoc! {"
17886        struct TestStruct {
17887            field: i32
17888        }
17889
17890        fn mainˇ() {
17891            let unused_var = 42;
17892            let test_struct = TestStruct { field: 42 };
17893        }
17894    "});
17895    let symbol_range = cx.lsp_range(indoc! {"
17896        struct TestStruct {
17897            field: i32
17898        }
17899
17900        «fn main»() {
17901            let unused_var = 42;
17902            let test_struct = TestStruct { field: 42 };
17903        }
17904    "});
17905    let mut hover_requests =
17906        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
17907            Ok(Some(lsp::Hover {
17908                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
17909                    kind: lsp::MarkupKind::Markdown,
17910                    value: "Function documentation".to_string(),
17911                }),
17912                range: Some(symbol_range),
17913            }))
17914        });
17915
17916    // Case 1: Test that code action menu hide hover popover
17917    cx.dispatch_action(Hover);
17918    hover_requests.next().await;
17919    cx.condition(|editor, _| editor.hover_state.visible()).await;
17920    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
17921        move |_, _, _| async move {
17922            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
17923                lsp::CodeAction {
17924                    title: "Remove unused variable".to_string(),
17925                    kind: Some(CodeActionKind::QUICKFIX),
17926                    edit: Some(lsp::WorkspaceEdit {
17927                        changes: Some(
17928                            [(
17929                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
17930                                vec![lsp::TextEdit {
17931                                    range: lsp::Range::new(
17932                                        lsp::Position::new(5, 4),
17933                                        lsp::Position::new(5, 27),
17934                                    ),
17935                                    new_text: "".to_string(),
17936                                }],
17937                            )]
17938                            .into_iter()
17939                            .collect(),
17940                        ),
17941                        ..Default::default()
17942                    }),
17943                    ..Default::default()
17944                },
17945            )]))
17946        },
17947    );
17948    cx.update_editor(|editor, window, cx| {
17949        editor.toggle_code_actions(
17950            &ToggleCodeActions {
17951                deployed_from: None,
17952                quick_launch: false,
17953            },
17954            window,
17955            cx,
17956        );
17957    });
17958    code_action_requests.next().await;
17959    cx.run_until_parked();
17960    cx.condition(|editor, _| editor.context_menu_visible())
17961        .await;
17962    cx.update_editor(|editor, _, _| {
17963        assert!(
17964            !editor.hover_state.visible(),
17965            "Hover popover should be hidden when code action menu is shown"
17966        );
17967        // Hide code actions
17968        editor.context_menu.take();
17969    });
17970
17971    // Case 2: Test that code completions hide hover popover
17972    cx.dispatch_action(Hover);
17973    hover_requests.next().await;
17974    cx.condition(|editor, _| editor.hover_state.visible()).await;
17975    let counter = Arc::new(AtomicUsize::new(0));
17976    let mut completion_requests =
17977        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17978            let counter = counter.clone();
17979            async move {
17980                counter.fetch_add(1, atomic::Ordering::Release);
17981                Ok(Some(lsp::CompletionResponse::Array(vec![
17982                    lsp::CompletionItem {
17983                        label: "main".into(),
17984                        kind: Some(lsp::CompletionItemKind::FUNCTION),
17985                        detail: Some("() -> ()".to_string()),
17986                        ..Default::default()
17987                    },
17988                    lsp::CompletionItem {
17989                        label: "TestStruct".into(),
17990                        kind: Some(lsp::CompletionItemKind::STRUCT),
17991                        detail: Some("struct TestStruct".to_string()),
17992                        ..Default::default()
17993                    },
17994                ])))
17995            }
17996        });
17997    cx.update_editor(|editor, window, cx| {
17998        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
17999    });
18000    completion_requests.next().await;
18001    cx.condition(|editor, _| editor.context_menu_visible())
18002        .await;
18003    cx.update_editor(|editor, _, _| {
18004        assert!(
18005            !editor.hover_state.visible(),
18006            "Hover popover should be hidden when completion menu is shown"
18007        );
18008    });
18009}
18010
18011#[gpui::test]
18012async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
18013    init_test(cx, |_| {});
18014
18015    let mut cx = EditorLspTestContext::new_rust(
18016        lsp::ServerCapabilities {
18017            completion_provider: Some(lsp::CompletionOptions {
18018                trigger_characters: Some(vec![".".to_string()]),
18019                resolve_provider: Some(true),
18020                ..Default::default()
18021            }),
18022            ..Default::default()
18023        },
18024        cx,
18025    )
18026    .await;
18027
18028    cx.set_state("fn main() { let a = 2ˇ; }");
18029    cx.simulate_keystroke(".");
18030
18031    let unresolved_item_1 = lsp::CompletionItem {
18032        label: "id".to_string(),
18033        filter_text: Some("id".to_string()),
18034        detail: None,
18035        documentation: None,
18036        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18037            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18038            new_text: ".id".to_string(),
18039        })),
18040        ..lsp::CompletionItem::default()
18041    };
18042    let resolved_item_1 = lsp::CompletionItem {
18043        additional_text_edits: Some(vec![lsp::TextEdit {
18044            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
18045            new_text: "!!".to_string(),
18046        }]),
18047        ..unresolved_item_1.clone()
18048    };
18049    let unresolved_item_2 = lsp::CompletionItem {
18050        label: "other".to_string(),
18051        filter_text: Some("other".to_string()),
18052        detail: None,
18053        documentation: None,
18054        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18055            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18056            new_text: ".other".to_string(),
18057        })),
18058        ..lsp::CompletionItem::default()
18059    };
18060    let resolved_item_2 = lsp::CompletionItem {
18061        additional_text_edits: Some(vec![lsp::TextEdit {
18062            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
18063            new_text: "??".to_string(),
18064        }]),
18065        ..unresolved_item_2.clone()
18066    };
18067
18068    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
18069    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
18070    cx.lsp
18071        .server
18072        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
18073            let unresolved_item_1 = unresolved_item_1.clone();
18074            let resolved_item_1 = resolved_item_1.clone();
18075            let unresolved_item_2 = unresolved_item_2.clone();
18076            let resolved_item_2 = resolved_item_2.clone();
18077            let resolve_requests_1 = resolve_requests_1.clone();
18078            let resolve_requests_2 = resolve_requests_2.clone();
18079            move |unresolved_request, _| {
18080                let unresolved_item_1 = unresolved_item_1.clone();
18081                let resolved_item_1 = resolved_item_1.clone();
18082                let unresolved_item_2 = unresolved_item_2.clone();
18083                let resolved_item_2 = resolved_item_2.clone();
18084                let resolve_requests_1 = resolve_requests_1.clone();
18085                let resolve_requests_2 = resolve_requests_2.clone();
18086                async move {
18087                    if unresolved_request == unresolved_item_1 {
18088                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
18089                        Ok(resolved_item_1.clone())
18090                    } else if unresolved_request == unresolved_item_2 {
18091                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
18092                        Ok(resolved_item_2.clone())
18093                    } else {
18094                        panic!("Unexpected completion item {unresolved_request:?}")
18095                    }
18096                }
18097            }
18098        })
18099        .detach();
18100
18101    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18102        let unresolved_item_1 = unresolved_item_1.clone();
18103        let unresolved_item_2 = unresolved_item_2.clone();
18104        async move {
18105            Ok(Some(lsp::CompletionResponse::Array(vec![
18106                unresolved_item_1,
18107                unresolved_item_2,
18108            ])))
18109        }
18110    })
18111    .next()
18112    .await;
18113
18114    cx.condition(|editor, _| editor.context_menu_visible())
18115        .await;
18116    cx.update_editor(|editor, _, _| {
18117        let context_menu = editor.context_menu.borrow_mut();
18118        let context_menu = context_menu
18119            .as_ref()
18120            .expect("Should have the context menu deployed");
18121        match context_menu {
18122            CodeContextMenu::Completions(completions_menu) => {
18123                let completions = completions_menu.completions.borrow_mut();
18124                assert_eq!(
18125                    completions
18126                        .iter()
18127                        .map(|completion| &completion.label.text)
18128                        .collect::<Vec<_>>(),
18129                    vec!["id", "other"]
18130                )
18131            }
18132            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18133        }
18134    });
18135    cx.run_until_parked();
18136
18137    cx.update_editor(|editor, window, cx| {
18138        editor.context_menu_next(&ContextMenuNext, window, cx);
18139    });
18140    cx.run_until_parked();
18141    cx.update_editor(|editor, window, cx| {
18142        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
18143    });
18144    cx.run_until_parked();
18145    cx.update_editor(|editor, window, cx| {
18146        editor.context_menu_next(&ContextMenuNext, window, cx);
18147    });
18148    cx.run_until_parked();
18149    cx.update_editor(|editor, window, cx| {
18150        editor
18151            .compose_completion(&ComposeCompletion::default(), window, cx)
18152            .expect("No task returned")
18153    })
18154    .await
18155    .expect("Completion failed");
18156    cx.run_until_parked();
18157
18158    cx.update_editor(|editor, _, cx| {
18159        assert_eq!(
18160            resolve_requests_1.load(atomic::Ordering::Acquire),
18161            1,
18162            "Should always resolve once despite multiple selections"
18163        );
18164        assert_eq!(
18165            resolve_requests_2.load(atomic::Ordering::Acquire),
18166            1,
18167            "Should always resolve once after multiple selections and applying the completion"
18168        );
18169        assert_eq!(
18170            editor.text(cx),
18171            "fn main() { let a = ??.other; }",
18172            "Should use resolved data when applying the completion"
18173        );
18174    });
18175}
18176
18177#[gpui::test]
18178async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
18179    init_test(cx, |_| {});
18180
18181    let item_0 = lsp::CompletionItem {
18182        label: "abs".into(),
18183        insert_text: Some("abs".into()),
18184        data: Some(json!({ "very": "special"})),
18185        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
18186        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
18187            lsp::InsertReplaceEdit {
18188                new_text: "abs".to_string(),
18189                insert: lsp::Range::default(),
18190                replace: lsp::Range::default(),
18191            },
18192        )),
18193        ..lsp::CompletionItem::default()
18194    };
18195    let items = iter::once(item_0.clone())
18196        .chain((11..51).map(|i| lsp::CompletionItem {
18197            label: format!("item_{}", i),
18198            insert_text: Some(format!("item_{}", i)),
18199            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
18200            ..lsp::CompletionItem::default()
18201        }))
18202        .collect::<Vec<_>>();
18203
18204    let default_commit_characters = vec!["?".to_string()];
18205    let default_data = json!({ "default": "data"});
18206    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
18207    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
18208    let default_edit_range = lsp::Range {
18209        start: lsp::Position {
18210            line: 0,
18211            character: 5,
18212        },
18213        end: lsp::Position {
18214            line: 0,
18215            character: 5,
18216        },
18217    };
18218
18219    let mut cx = EditorLspTestContext::new_rust(
18220        lsp::ServerCapabilities {
18221            completion_provider: Some(lsp::CompletionOptions {
18222                trigger_characters: Some(vec![".".to_string()]),
18223                resolve_provider: Some(true),
18224                ..Default::default()
18225            }),
18226            ..Default::default()
18227        },
18228        cx,
18229    )
18230    .await;
18231
18232    cx.set_state("fn main() { let a = 2ˇ; }");
18233    cx.simulate_keystroke(".");
18234
18235    let completion_data = default_data.clone();
18236    let completion_characters = default_commit_characters.clone();
18237    let completion_items = items.clone();
18238    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18239        let default_data = completion_data.clone();
18240        let default_commit_characters = completion_characters.clone();
18241        let items = completion_items.clone();
18242        async move {
18243            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
18244                items,
18245                item_defaults: Some(lsp::CompletionListItemDefaults {
18246                    data: Some(default_data.clone()),
18247                    commit_characters: Some(default_commit_characters.clone()),
18248                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
18249                        default_edit_range,
18250                    )),
18251                    insert_text_format: Some(default_insert_text_format),
18252                    insert_text_mode: Some(default_insert_text_mode),
18253                }),
18254                ..lsp::CompletionList::default()
18255            })))
18256        }
18257    })
18258    .next()
18259    .await;
18260
18261    let resolved_items = Arc::new(Mutex::new(Vec::new()));
18262    cx.lsp
18263        .server
18264        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
18265            let closure_resolved_items = resolved_items.clone();
18266            move |item_to_resolve, _| {
18267                let closure_resolved_items = closure_resolved_items.clone();
18268                async move {
18269                    closure_resolved_items.lock().push(item_to_resolve.clone());
18270                    Ok(item_to_resolve)
18271                }
18272            }
18273        })
18274        .detach();
18275
18276    cx.condition(|editor, _| editor.context_menu_visible())
18277        .await;
18278    cx.run_until_parked();
18279    cx.update_editor(|editor, _, _| {
18280        let menu = editor.context_menu.borrow_mut();
18281        match menu.as_ref().expect("should have the completions menu") {
18282            CodeContextMenu::Completions(completions_menu) => {
18283                assert_eq!(
18284                    completions_menu
18285                        .entries
18286                        .borrow()
18287                        .iter()
18288                        .map(|mat| mat.string.clone())
18289                        .collect::<Vec<String>>(),
18290                    items
18291                        .iter()
18292                        .map(|completion| completion.label.clone())
18293                        .collect::<Vec<String>>()
18294                );
18295            }
18296            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
18297        }
18298    });
18299    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
18300    // with 4 from the end.
18301    assert_eq!(
18302        *resolved_items.lock(),
18303        [&items[0..16], &items[items.len() - 4..items.len()]]
18304            .concat()
18305            .iter()
18306            .cloned()
18307            .map(|mut item| {
18308                if item.data.is_none() {
18309                    item.data = Some(default_data.clone());
18310                }
18311                item
18312            })
18313            .collect::<Vec<lsp::CompletionItem>>(),
18314        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
18315    );
18316    resolved_items.lock().clear();
18317
18318    cx.update_editor(|editor, window, cx| {
18319        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
18320    });
18321    cx.run_until_parked();
18322    // Completions that have already been resolved are skipped.
18323    assert_eq!(
18324        *resolved_items.lock(),
18325        items[items.len() - 17..items.len() - 4]
18326            .iter()
18327            .cloned()
18328            .map(|mut item| {
18329                if item.data.is_none() {
18330                    item.data = Some(default_data.clone());
18331                }
18332                item
18333            })
18334            .collect::<Vec<lsp::CompletionItem>>()
18335    );
18336    resolved_items.lock().clear();
18337}
18338
18339#[gpui::test]
18340async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
18341    init_test(cx, |_| {});
18342
18343    let mut cx = EditorLspTestContext::new(
18344        Language::new(
18345            LanguageConfig {
18346                matcher: LanguageMatcher {
18347                    path_suffixes: vec!["jsx".into()],
18348                    ..Default::default()
18349                },
18350                overrides: [(
18351                    "element".into(),
18352                    LanguageConfigOverride {
18353                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
18354                        ..Default::default()
18355                    },
18356                )]
18357                .into_iter()
18358                .collect(),
18359                ..Default::default()
18360            },
18361            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
18362        )
18363        .with_override_query("(jsx_self_closing_element) @element")
18364        .unwrap(),
18365        lsp::ServerCapabilities {
18366            completion_provider: Some(lsp::CompletionOptions {
18367                trigger_characters: Some(vec![":".to_string()]),
18368                ..Default::default()
18369            }),
18370            ..Default::default()
18371        },
18372        cx,
18373    )
18374    .await;
18375
18376    cx.lsp
18377        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
18378            Ok(Some(lsp::CompletionResponse::Array(vec![
18379                lsp::CompletionItem {
18380                    label: "bg-blue".into(),
18381                    ..Default::default()
18382                },
18383                lsp::CompletionItem {
18384                    label: "bg-red".into(),
18385                    ..Default::default()
18386                },
18387                lsp::CompletionItem {
18388                    label: "bg-yellow".into(),
18389                    ..Default::default()
18390                },
18391            ])))
18392        });
18393
18394    cx.set_state(r#"<p class="bgˇ" />"#);
18395
18396    // Trigger completion when typing a dash, because the dash is an extra
18397    // word character in the 'element' scope, which contains the cursor.
18398    cx.simulate_keystroke("-");
18399    cx.executor().run_until_parked();
18400    cx.update_editor(|editor, _, _| {
18401        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18402        {
18403            assert_eq!(
18404                completion_menu_entries(menu),
18405                &["bg-blue", "bg-red", "bg-yellow"]
18406            );
18407        } else {
18408            panic!("expected completion menu to be open");
18409        }
18410    });
18411
18412    cx.simulate_keystroke("l");
18413    cx.executor().run_until_parked();
18414    cx.update_editor(|editor, _, _| {
18415        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18416        {
18417            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
18418        } else {
18419            panic!("expected completion menu to be open");
18420        }
18421    });
18422
18423    // When filtering completions, consider the character after the '-' to
18424    // be the start of a subword.
18425    cx.set_state(r#"<p class="yelˇ" />"#);
18426    cx.simulate_keystroke("l");
18427    cx.executor().run_until_parked();
18428    cx.update_editor(|editor, _, _| {
18429        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18430        {
18431            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
18432        } else {
18433            panic!("expected completion menu to be open");
18434        }
18435    });
18436}
18437
18438fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
18439    let entries = menu.entries.borrow();
18440    entries.iter().map(|mat| mat.string.clone()).collect()
18441}
18442
18443#[gpui::test]
18444async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
18445    init_test(cx, |settings| {
18446        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
18447    });
18448
18449    let fs = FakeFs::new(cx.executor());
18450    fs.insert_file(path!("/file.ts"), Default::default()).await;
18451
18452    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
18453    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18454
18455    language_registry.add(Arc::new(Language::new(
18456        LanguageConfig {
18457            name: "TypeScript".into(),
18458            matcher: LanguageMatcher {
18459                path_suffixes: vec!["ts".to_string()],
18460                ..Default::default()
18461            },
18462            ..Default::default()
18463        },
18464        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
18465    )));
18466    update_test_language_settings(cx, |settings| {
18467        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
18468    });
18469
18470    let test_plugin = "test_plugin";
18471    let _ = language_registry.register_fake_lsp(
18472        "TypeScript",
18473        FakeLspAdapter {
18474            prettier_plugins: vec![test_plugin],
18475            ..Default::default()
18476        },
18477    );
18478
18479    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
18480    let buffer = project
18481        .update(cx, |project, cx| {
18482            project.open_local_buffer(path!("/file.ts"), cx)
18483        })
18484        .await
18485        .unwrap();
18486
18487    let buffer_text = "one\ntwo\nthree\n";
18488    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
18489    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
18490    editor.update_in(cx, |editor, window, cx| {
18491        editor.set_text(buffer_text, window, cx)
18492    });
18493
18494    editor
18495        .update_in(cx, |editor, window, cx| {
18496            editor.perform_format(
18497                project.clone(),
18498                FormatTrigger::Manual,
18499                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18500                window,
18501                cx,
18502            )
18503        })
18504        .unwrap()
18505        .await;
18506    assert_eq!(
18507        editor.update(cx, |editor, cx| editor.text(cx)),
18508        buffer_text.to_string() + prettier_format_suffix,
18509        "Test prettier formatting was not applied to the original buffer text",
18510    );
18511
18512    update_test_language_settings(cx, |settings| {
18513        settings.defaults.formatter = Some(FormatterList::default())
18514    });
18515    let format = editor.update_in(cx, |editor, window, cx| {
18516        editor.perform_format(
18517            project.clone(),
18518            FormatTrigger::Manual,
18519            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18520            window,
18521            cx,
18522        )
18523    });
18524    format.await.unwrap();
18525    assert_eq!(
18526        editor.update(cx, |editor, cx| editor.text(cx)),
18527        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
18528        "Autoformatting (via test prettier) was not applied to the original buffer text",
18529    );
18530}
18531
18532#[gpui::test]
18533async fn test_addition_reverts(cx: &mut TestAppContext) {
18534    init_test(cx, |_| {});
18535    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18536    let base_text = indoc! {r#"
18537        struct Row;
18538        struct Row1;
18539        struct Row2;
18540
18541        struct Row4;
18542        struct Row5;
18543        struct Row6;
18544
18545        struct Row8;
18546        struct Row9;
18547        struct Row10;"#};
18548
18549    // When addition hunks are not adjacent to carets, no hunk revert is performed
18550    assert_hunk_revert(
18551        indoc! {r#"struct Row;
18552                   struct Row1;
18553                   struct Row1.1;
18554                   struct Row1.2;
18555                   struct Row2;ˇ
18556
18557                   struct Row4;
18558                   struct Row5;
18559                   struct Row6;
18560
18561                   struct Row8;
18562                   ˇstruct Row9;
18563                   struct Row9.1;
18564                   struct Row9.2;
18565                   struct Row9.3;
18566                   struct Row10;"#},
18567        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18568        indoc! {r#"struct Row;
18569                   struct Row1;
18570                   struct Row1.1;
18571                   struct Row1.2;
18572                   struct Row2;ˇ
18573
18574                   struct Row4;
18575                   struct Row5;
18576                   struct Row6;
18577
18578                   struct Row8;
18579                   ˇstruct Row9;
18580                   struct Row9.1;
18581                   struct Row9.2;
18582                   struct Row9.3;
18583                   struct Row10;"#},
18584        base_text,
18585        &mut cx,
18586    );
18587    // Same for selections
18588    assert_hunk_revert(
18589        indoc! {r#"struct Row;
18590                   struct Row1;
18591                   struct Row2;
18592                   struct Row2.1;
18593                   struct Row2.2;
18594                   «ˇ
18595                   struct Row4;
18596                   struct» Row5;
18597                   «struct Row6;
18598                   ˇ»
18599                   struct Row9.1;
18600                   struct Row9.2;
18601                   struct Row9.3;
18602                   struct Row8;
18603                   struct Row9;
18604                   struct Row10;"#},
18605        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18606        indoc! {r#"struct Row;
18607                   struct Row1;
18608                   struct Row2;
18609                   struct Row2.1;
18610                   struct Row2.2;
18611                   «ˇ
18612                   struct Row4;
18613                   struct» Row5;
18614                   «struct Row6;
18615                   ˇ»
18616                   struct Row9.1;
18617                   struct Row9.2;
18618                   struct Row9.3;
18619                   struct Row8;
18620                   struct Row9;
18621                   struct Row10;"#},
18622        base_text,
18623        &mut cx,
18624    );
18625
18626    // When carets and selections intersect the addition hunks, those are reverted.
18627    // Adjacent carets got merged.
18628    assert_hunk_revert(
18629        indoc! {r#"struct Row;
18630                   ˇ// something on the top
18631                   struct Row1;
18632                   struct Row2;
18633                   struct Roˇw3.1;
18634                   struct Row2.2;
18635                   struct Row2.3;ˇ
18636
18637                   struct Row4;
18638                   struct ˇRow5.1;
18639                   struct Row5.2;
18640                   struct «Rowˇ»5.3;
18641                   struct Row5;
18642                   struct Row6;
18643                   ˇ
18644                   struct Row9.1;
18645                   struct «Rowˇ»9.2;
18646                   struct «ˇRow»9.3;
18647                   struct Row8;
18648                   struct Row9;
18649                   «ˇ// something on bottom»
18650                   struct Row10;"#},
18651        vec![
18652            DiffHunkStatusKind::Added,
18653            DiffHunkStatusKind::Added,
18654            DiffHunkStatusKind::Added,
18655            DiffHunkStatusKind::Added,
18656            DiffHunkStatusKind::Added,
18657        ],
18658        indoc! {r#"struct Row;
18659                   ˇstruct Row1;
18660                   struct Row2;
18661                   ˇ
18662                   struct Row4;
18663                   ˇstruct Row5;
18664                   struct Row6;
18665                   ˇ
18666                   ˇstruct Row8;
18667                   struct Row9;
18668                   ˇstruct Row10;"#},
18669        base_text,
18670        &mut cx,
18671    );
18672}
18673
18674#[gpui::test]
18675async fn test_modification_reverts(cx: &mut TestAppContext) {
18676    init_test(cx, |_| {});
18677    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18678    let base_text = indoc! {r#"
18679        struct Row;
18680        struct Row1;
18681        struct Row2;
18682
18683        struct Row4;
18684        struct Row5;
18685        struct Row6;
18686
18687        struct Row8;
18688        struct Row9;
18689        struct Row10;"#};
18690
18691    // Modification hunks behave the same as the addition ones.
18692    assert_hunk_revert(
18693        indoc! {r#"struct Row;
18694                   struct Row1;
18695                   struct Row33;
18696                   ˇ
18697                   struct Row4;
18698                   struct Row5;
18699                   struct Row6;
18700                   ˇ
18701                   struct Row99;
18702                   struct Row9;
18703                   struct Row10;"#},
18704        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18705        indoc! {r#"struct Row;
18706                   struct Row1;
18707                   struct Row33;
18708                   ˇ
18709                   struct Row4;
18710                   struct Row5;
18711                   struct Row6;
18712                   ˇ
18713                   struct Row99;
18714                   struct Row9;
18715                   struct Row10;"#},
18716        base_text,
18717        &mut cx,
18718    );
18719    assert_hunk_revert(
18720        indoc! {r#"struct Row;
18721                   struct Row1;
18722                   struct Row33;
18723                   «ˇ
18724                   struct Row4;
18725                   struct» Row5;
18726                   «struct Row6;
18727                   ˇ»
18728                   struct Row99;
18729                   struct Row9;
18730                   struct Row10;"#},
18731        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18732        indoc! {r#"struct Row;
18733                   struct Row1;
18734                   struct Row33;
18735                   «ˇ
18736                   struct Row4;
18737                   struct» Row5;
18738                   «struct Row6;
18739                   ˇ»
18740                   struct Row99;
18741                   struct Row9;
18742                   struct Row10;"#},
18743        base_text,
18744        &mut cx,
18745    );
18746
18747    assert_hunk_revert(
18748        indoc! {r#"ˇstruct Row1.1;
18749                   struct Row1;
18750                   «ˇstr»uct Row22;
18751
18752                   struct ˇRow44;
18753                   struct Row5;
18754                   struct «Rˇ»ow66;ˇ
18755
18756                   «struˇ»ct Row88;
18757                   struct Row9;
18758                   struct Row1011;ˇ"#},
18759        vec![
18760            DiffHunkStatusKind::Modified,
18761            DiffHunkStatusKind::Modified,
18762            DiffHunkStatusKind::Modified,
18763            DiffHunkStatusKind::Modified,
18764            DiffHunkStatusKind::Modified,
18765            DiffHunkStatusKind::Modified,
18766        ],
18767        indoc! {r#"struct Row;
18768                   ˇstruct Row1;
18769                   struct Row2;
18770                   ˇ
18771                   struct Row4;
18772                   ˇstruct Row5;
18773                   struct Row6;
18774                   ˇ
18775                   struct Row8;
18776                   ˇstruct Row9;
18777                   struct Row10;ˇ"#},
18778        base_text,
18779        &mut cx,
18780    );
18781}
18782
18783#[gpui::test]
18784async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
18785    init_test(cx, |_| {});
18786    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18787    let base_text = indoc! {r#"
18788        one
18789
18790        two
18791        three
18792        "#};
18793
18794    cx.set_head_text(base_text);
18795    cx.set_state("\nˇ\n");
18796    cx.executor().run_until_parked();
18797    cx.update_editor(|editor, _window, cx| {
18798        editor.expand_selected_diff_hunks(cx);
18799    });
18800    cx.executor().run_until_parked();
18801    cx.update_editor(|editor, window, cx| {
18802        editor.backspace(&Default::default(), window, cx);
18803    });
18804    cx.run_until_parked();
18805    cx.assert_state_with_diff(
18806        indoc! {r#"
18807
18808        - two
18809        - threeˇ
18810        +
18811        "#}
18812        .to_string(),
18813    );
18814}
18815
18816#[gpui::test]
18817async fn test_deletion_reverts(cx: &mut TestAppContext) {
18818    init_test(cx, |_| {});
18819    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18820    let base_text = indoc! {r#"struct Row;
18821struct Row1;
18822struct Row2;
18823
18824struct Row4;
18825struct Row5;
18826struct Row6;
18827
18828struct Row8;
18829struct Row9;
18830struct Row10;"#};
18831
18832    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
18833    assert_hunk_revert(
18834        indoc! {r#"struct Row;
18835                   struct Row2;
18836
18837                   ˇstruct Row4;
18838                   struct Row5;
18839                   struct Row6;
18840                   ˇ
18841                   struct Row8;
18842                   struct Row10;"#},
18843        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18844        indoc! {r#"struct Row;
18845                   struct Row2;
18846
18847                   ˇstruct Row4;
18848                   struct Row5;
18849                   struct Row6;
18850                   ˇ
18851                   struct Row8;
18852                   struct Row10;"#},
18853        base_text,
18854        &mut cx,
18855    );
18856    assert_hunk_revert(
18857        indoc! {r#"struct Row;
18858                   struct Row2;
18859
18860                   «ˇstruct Row4;
18861                   struct» Row5;
18862                   «struct Row6;
18863                   ˇ»
18864                   struct Row8;
18865                   struct Row10;"#},
18866        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18867        indoc! {r#"struct Row;
18868                   struct Row2;
18869
18870                   «ˇstruct Row4;
18871                   struct» Row5;
18872                   «struct Row6;
18873                   ˇ»
18874                   struct Row8;
18875                   struct Row10;"#},
18876        base_text,
18877        &mut cx,
18878    );
18879
18880    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
18881    assert_hunk_revert(
18882        indoc! {r#"struct Row;
18883                   ˇstruct Row2;
18884
18885                   struct Row4;
18886                   struct Row5;
18887                   struct Row6;
18888
18889                   struct Row8;ˇ
18890                   struct Row10;"#},
18891        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18892        indoc! {r#"struct Row;
18893                   struct Row1;
18894                   ˇstruct Row2;
18895
18896                   struct Row4;
18897                   struct Row5;
18898                   struct Row6;
18899
18900                   struct Row8;ˇ
18901                   struct Row9;
18902                   struct Row10;"#},
18903        base_text,
18904        &mut cx,
18905    );
18906    assert_hunk_revert(
18907        indoc! {r#"struct Row;
18908                   struct Row2«ˇ;
18909                   struct Row4;
18910                   struct» Row5;
18911                   «struct Row6;
18912
18913                   struct Row8;ˇ»
18914                   struct Row10;"#},
18915        vec![
18916            DiffHunkStatusKind::Deleted,
18917            DiffHunkStatusKind::Deleted,
18918            DiffHunkStatusKind::Deleted,
18919        ],
18920        indoc! {r#"struct Row;
18921                   struct Row1;
18922                   struct Row2«ˇ;
18923
18924                   struct Row4;
18925                   struct» Row5;
18926                   «struct Row6;
18927
18928                   struct Row8;ˇ»
18929                   struct Row9;
18930                   struct Row10;"#},
18931        base_text,
18932        &mut cx,
18933    );
18934}
18935
18936#[gpui::test]
18937async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
18938    init_test(cx, |_| {});
18939
18940    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
18941    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
18942    let base_text_3 =
18943        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
18944
18945    let text_1 = edit_first_char_of_every_line(base_text_1);
18946    let text_2 = edit_first_char_of_every_line(base_text_2);
18947    let text_3 = edit_first_char_of_every_line(base_text_3);
18948
18949    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
18950    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
18951    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
18952
18953    let multibuffer = cx.new(|cx| {
18954        let mut multibuffer = MultiBuffer::new(ReadWrite);
18955        multibuffer.push_excerpts(
18956            buffer_1.clone(),
18957            [
18958                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18959                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18960                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18961            ],
18962            cx,
18963        );
18964        multibuffer.push_excerpts(
18965            buffer_2.clone(),
18966            [
18967                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18968                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18969                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18970            ],
18971            cx,
18972        );
18973        multibuffer.push_excerpts(
18974            buffer_3.clone(),
18975            [
18976                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18977                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18978                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18979            ],
18980            cx,
18981        );
18982        multibuffer
18983    });
18984
18985    let fs = FakeFs::new(cx.executor());
18986    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
18987    let (editor, cx) = cx
18988        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
18989    editor.update_in(cx, |editor, _window, cx| {
18990        for (buffer, diff_base) in [
18991            (buffer_1.clone(), base_text_1),
18992            (buffer_2.clone(), base_text_2),
18993            (buffer_3.clone(), base_text_3),
18994        ] {
18995            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
18996            editor
18997                .buffer
18998                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
18999        }
19000    });
19001    cx.executor().run_until_parked();
19002
19003    editor.update_in(cx, |editor, window, cx| {
19004        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}");
19005        editor.select_all(&SelectAll, window, cx);
19006        editor.git_restore(&Default::default(), window, cx);
19007    });
19008    cx.executor().run_until_parked();
19009
19010    // When all ranges are selected, all buffer hunks are reverted.
19011    editor.update(cx, |editor, cx| {
19012        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");
19013    });
19014    buffer_1.update(cx, |buffer, _| {
19015        assert_eq!(buffer.text(), base_text_1);
19016    });
19017    buffer_2.update(cx, |buffer, _| {
19018        assert_eq!(buffer.text(), base_text_2);
19019    });
19020    buffer_3.update(cx, |buffer, _| {
19021        assert_eq!(buffer.text(), base_text_3);
19022    });
19023
19024    editor.update_in(cx, |editor, window, cx| {
19025        editor.undo(&Default::default(), window, cx);
19026    });
19027
19028    editor.update_in(cx, |editor, window, cx| {
19029        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19030            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
19031        });
19032        editor.git_restore(&Default::default(), window, cx);
19033    });
19034
19035    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
19036    // but not affect buffer_2 and its related excerpts.
19037    editor.update(cx, |editor, cx| {
19038        assert_eq!(
19039            editor.text(cx),
19040            "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}"
19041        );
19042    });
19043    buffer_1.update(cx, |buffer, _| {
19044        assert_eq!(buffer.text(), base_text_1);
19045    });
19046    buffer_2.update(cx, |buffer, _| {
19047        assert_eq!(
19048            buffer.text(),
19049            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
19050        );
19051    });
19052    buffer_3.update(cx, |buffer, _| {
19053        assert_eq!(
19054            buffer.text(),
19055            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
19056        );
19057    });
19058
19059    fn edit_first_char_of_every_line(text: &str) -> String {
19060        text.split('\n')
19061            .map(|line| format!("X{}", &line[1..]))
19062            .collect::<Vec<_>>()
19063            .join("\n")
19064    }
19065}
19066
19067#[gpui::test]
19068async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
19069    init_test(cx, |_| {});
19070
19071    let cols = 4;
19072    let rows = 10;
19073    let sample_text_1 = sample_text(rows, cols, 'a');
19074    assert_eq!(
19075        sample_text_1,
19076        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
19077    );
19078    let sample_text_2 = sample_text(rows, cols, 'l');
19079    assert_eq!(
19080        sample_text_2,
19081        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
19082    );
19083    let sample_text_3 = sample_text(rows, cols, 'v');
19084    assert_eq!(
19085        sample_text_3,
19086        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
19087    );
19088
19089    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
19090    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
19091    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
19092
19093    let multi_buffer = cx.new(|cx| {
19094        let mut multibuffer = MultiBuffer::new(ReadWrite);
19095        multibuffer.push_excerpts(
19096            buffer_1.clone(),
19097            [
19098                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19099                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19100                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19101            ],
19102            cx,
19103        );
19104        multibuffer.push_excerpts(
19105            buffer_2.clone(),
19106            [
19107                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19108                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19109                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19110            ],
19111            cx,
19112        );
19113        multibuffer.push_excerpts(
19114            buffer_3.clone(),
19115            [
19116                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19117                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19118                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19119            ],
19120            cx,
19121        );
19122        multibuffer
19123    });
19124
19125    let fs = FakeFs::new(cx.executor());
19126    fs.insert_tree(
19127        "/a",
19128        json!({
19129            "main.rs": sample_text_1,
19130            "other.rs": sample_text_2,
19131            "lib.rs": sample_text_3,
19132        }),
19133    )
19134    .await;
19135    let project = Project::test(fs, ["/a".as_ref()], cx).await;
19136    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19137    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19138    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19139        Editor::new(
19140            EditorMode::full(),
19141            multi_buffer,
19142            Some(project.clone()),
19143            window,
19144            cx,
19145        )
19146    });
19147    let multibuffer_item_id = workspace
19148        .update(cx, |workspace, window, cx| {
19149            assert!(
19150                workspace.active_item(cx).is_none(),
19151                "active item should be None before the first item is added"
19152            );
19153            workspace.add_item_to_active_pane(
19154                Box::new(multi_buffer_editor.clone()),
19155                None,
19156                true,
19157                window,
19158                cx,
19159            );
19160            let active_item = workspace
19161                .active_item(cx)
19162                .expect("should have an active item after adding the multi buffer");
19163            assert_eq!(
19164                active_item.buffer_kind(cx),
19165                ItemBufferKind::Multibuffer,
19166                "A multi buffer was expected to active after adding"
19167            );
19168            active_item.item_id()
19169        })
19170        .unwrap();
19171    cx.executor().run_until_parked();
19172
19173    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19174        editor.change_selections(
19175            SelectionEffects::scroll(Autoscroll::Next),
19176            window,
19177            cx,
19178            |s| s.select_ranges(Some(1..2)),
19179        );
19180        editor.open_excerpts(&OpenExcerpts, window, cx);
19181    });
19182    cx.executor().run_until_parked();
19183    let first_item_id = workspace
19184        .update(cx, |workspace, window, cx| {
19185            let active_item = workspace
19186                .active_item(cx)
19187                .expect("should have an active item after navigating into the 1st buffer");
19188            let first_item_id = active_item.item_id();
19189            assert_ne!(
19190                first_item_id, multibuffer_item_id,
19191                "Should navigate into the 1st buffer and activate it"
19192            );
19193            assert_eq!(
19194                active_item.buffer_kind(cx),
19195                ItemBufferKind::Singleton,
19196                "New active item should be a singleton buffer"
19197            );
19198            assert_eq!(
19199                active_item
19200                    .act_as::<Editor>(cx)
19201                    .expect("should have navigated into an editor for the 1st buffer")
19202                    .read(cx)
19203                    .text(cx),
19204                sample_text_1
19205            );
19206
19207            workspace
19208                .go_back(workspace.active_pane().downgrade(), window, cx)
19209                .detach_and_log_err(cx);
19210
19211            first_item_id
19212        })
19213        .unwrap();
19214    cx.executor().run_until_parked();
19215    workspace
19216        .update(cx, |workspace, _, cx| {
19217            let active_item = workspace
19218                .active_item(cx)
19219                .expect("should have an active item after navigating back");
19220            assert_eq!(
19221                active_item.item_id(),
19222                multibuffer_item_id,
19223                "Should navigate back to the multi buffer"
19224            );
19225            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19226        })
19227        .unwrap();
19228
19229    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19230        editor.change_selections(
19231            SelectionEffects::scroll(Autoscroll::Next),
19232            window,
19233            cx,
19234            |s| s.select_ranges(Some(39..40)),
19235        );
19236        editor.open_excerpts(&OpenExcerpts, window, cx);
19237    });
19238    cx.executor().run_until_parked();
19239    let second_item_id = workspace
19240        .update(cx, |workspace, window, cx| {
19241            let active_item = workspace
19242                .active_item(cx)
19243                .expect("should have an active item after navigating into the 2nd buffer");
19244            let second_item_id = active_item.item_id();
19245            assert_ne!(
19246                second_item_id, multibuffer_item_id,
19247                "Should navigate away from the multibuffer"
19248            );
19249            assert_ne!(
19250                second_item_id, first_item_id,
19251                "Should navigate into the 2nd buffer and activate it"
19252            );
19253            assert_eq!(
19254                active_item.buffer_kind(cx),
19255                ItemBufferKind::Singleton,
19256                "New active item should be a singleton buffer"
19257            );
19258            assert_eq!(
19259                active_item
19260                    .act_as::<Editor>(cx)
19261                    .expect("should have navigated into an editor")
19262                    .read(cx)
19263                    .text(cx),
19264                sample_text_2
19265            );
19266
19267            workspace
19268                .go_back(workspace.active_pane().downgrade(), window, cx)
19269                .detach_and_log_err(cx);
19270
19271            second_item_id
19272        })
19273        .unwrap();
19274    cx.executor().run_until_parked();
19275    workspace
19276        .update(cx, |workspace, _, cx| {
19277            let active_item = workspace
19278                .active_item(cx)
19279                .expect("should have an active item after navigating back from the 2nd buffer");
19280            assert_eq!(
19281                active_item.item_id(),
19282                multibuffer_item_id,
19283                "Should navigate back from the 2nd buffer to the multi buffer"
19284            );
19285            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19286        })
19287        .unwrap();
19288
19289    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19290        editor.change_selections(
19291            SelectionEffects::scroll(Autoscroll::Next),
19292            window,
19293            cx,
19294            |s| s.select_ranges(Some(70..70)),
19295        );
19296        editor.open_excerpts(&OpenExcerpts, window, cx);
19297    });
19298    cx.executor().run_until_parked();
19299    workspace
19300        .update(cx, |workspace, window, cx| {
19301            let active_item = workspace
19302                .active_item(cx)
19303                .expect("should have an active item after navigating into the 3rd buffer");
19304            let third_item_id = active_item.item_id();
19305            assert_ne!(
19306                third_item_id, multibuffer_item_id,
19307                "Should navigate into the 3rd buffer and activate it"
19308            );
19309            assert_ne!(third_item_id, first_item_id);
19310            assert_ne!(third_item_id, second_item_id);
19311            assert_eq!(
19312                active_item.buffer_kind(cx),
19313                ItemBufferKind::Singleton,
19314                "New active item should be a singleton buffer"
19315            );
19316            assert_eq!(
19317                active_item
19318                    .act_as::<Editor>(cx)
19319                    .expect("should have navigated into an editor")
19320                    .read(cx)
19321                    .text(cx),
19322                sample_text_3
19323            );
19324
19325            workspace
19326                .go_back(workspace.active_pane().downgrade(), window, cx)
19327                .detach_and_log_err(cx);
19328        })
19329        .unwrap();
19330    cx.executor().run_until_parked();
19331    workspace
19332        .update(cx, |workspace, _, cx| {
19333            let active_item = workspace
19334                .active_item(cx)
19335                .expect("should have an active item after navigating back from the 3rd buffer");
19336            assert_eq!(
19337                active_item.item_id(),
19338                multibuffer_item_id,
19339                "Should navigate back from the 3rd buffer to the multi buffer"
19340            );
19341            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19342        })
19343        .unwrap();
19344}
19345
19346#[gpui::test]
19347async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19348    init_test(cx, |_| {});
19349
19350    let mut cx = EditorTestContext::new(cx).await;
19351
19352    let diff_base = r#"
19353        use some::mod;
19354
19355        const A: u32 = 42;
19356
19357        fn main() {
19358            println!("hello");
19359
19360            println!("world");
19361        }
19362        "#
19363    .unindent();
19364
19365    cx.set_state(
19366        &r#"
19367        use some::modified;
19368
19369        ˇ
19370        fn main() {
19371            println!("hello there");
19372
19373            println!("around the");
19374            println!("world");
19375        }
19376        "#
19377        .unindent(),
19378    );
19379
19380    cx.set_head_text(&diff_base);
19381    executor.run_until_parked();
19382
19383    cx.update_editor(|editor, window, cx| {
19384        editor.go_to_next_hunk(&GoToHunk, window, cx);
19385        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19386    });
19387    executor.run_until_parked();
19388    cx.assert_state_with_diff(
19389        r#"
19390          use some::modified;
19391
19392
19393          fn main() {
19394        -     println!("hello");
19395        + ˇ    println!("hello there");
19396
19397              println!("around the");
19398              println!("world");
19399          }
19400        "#
19401        .unindent(),
19402    );
19403
19404    cx.update_editor(|editor, window, cx| {
19405        for _ in 0..2 {
19406            editor.go_to_next_hunk(&GoToHunk, window, cx);
19407            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19408        }
19409    });
19410    executor.run_until_parked();
19411    cx.assert_state_with_diff(
19412        r#"
19413        - use some::mod;
19414        + ˇuse some::modified;
19415
19416
19417          fn main() {
19418        -     println!("hello");
19419        +     println!("hello there");
19420
19421        +     println!("around the");
19422              println!("world");
19423          }
19424        "#
19425        .unindent(),
19426    );
19427
19428    cx.update_editor(|editor, window, cx| {
19429        editor.go_to_next_hunk(&GoToHunk, window, cx);
19430        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19431    });
19432    executor.run_until_parked();
19433    cx.assert_state_with_diff(
19434        r#"
19435        - use some::mod;
19436        + use some::modified;
19437
19438        - const A: u32 = 42;
19439          ˇ
19440          fn main() {
19441        -     println!("hello");
19442        +     println!("hello there");
19443
19444        +     println!("around the");
19445              println!("world");
19446          }
19447        "#
19448        .unindent(),
19449    );
19450
19451    cx.update_editor(|editor, window, cx| {
19452        editor.cancel(&Cancel, window, cx);
19453    });
19454
19455    cx.assert_state_with_diff(
19456        r#"
19457          use some::modified;
19458
19459          ˇ
19460          fn main() {
19461              println!("hello there");
19462
19463              println!("around the");
19464              println!("world");
19465          }
19466        "#
19467        .unindent(),
19468    );
19469}
19470
19471#[gpui::test]
19472async fn test_diff_base_change_with_expanded_diff_hunks(
19473    executor: BackgroundExecutor,
19474    cx: &mut TestAppContext,
19475) {
19476    init_test(cx, |_| {});
19477
19478    let mut cx = EditorTestContext::new(cx).await;
19479
19480    let diff_base = r#"
19481        use some::mod1;
19482        use some::mod2;
19483
19484        const A: u32 = 42;
19485        const B: u32 = 42;
19486        const C: u32 = 42;
19487
19488        fn main() {
19489            println!("hello");
19490
19491            println!("world");
19492        }
19493        "#
19494    .unindent();
19495
19496    cx.set_state(
19497        &r#"
19498        use some::mod2;
19499
19500        const A: u32 = 42;
19501        const C: u32 = 42;
19502
19503        fn main(ˇ) {
19504            //println!("hello");
19505
19506            println!("world");
19507            //
19508            //
19509        }
19510        "#
19511        .unindent(),
19512    );
19513
19514    cx.set_head_text(&diff_base);
19515    executor.run_until_parked();
19516
19517    cx.update_editor(|editor, window, cx| {
19518        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19519    });
19520    executor.run_until_parked();
19521    cx.assert_state_with_diff(
19522        r#"
19523        - use some::mod1;
19524          use some::mod2;
19525
19526          const A: u32 = 42;
19527        - const B: u32 = 42;
19528          const C: u32 = 42;
19529
19530          fn main(ˇ) {
19531        -     println!("hello");
19532        +     //println!("hello");
19533
19534              println!("world");
19535        +     //
19536        +     //
19537          }
19538        "#
19539        .unindent(),
19540    );
19541
19542    cx.set_head_text("new diff base!");
19543    executor.run_until_parked();
19544    cx.assert_state_with_diff(
19545        r#"
19546        - new diff base!
19547        + use some::mod2;
19548        +
19549        + const A: u32 = 42;
19550        + const C: u32 = 42;
19551        +
19552        + fn main(ˇ) {
19553        +     //println!("hello");
19554        +
19555        +     println!("world");
19556        +     //
19557        +     //
19558        + }
19559        "#
19560        .unindent(),
19561    );
19562}
19563
19564#[gpui::test]
19565async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
19566    init_test(cx, |_| {});
19567
19568    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19569    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19570    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19571    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19572    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
19573    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
19574
19575    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
19576    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
19577    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
19578
19579    let multi_buffer = cx.new(|cx| {
19580        let mut multibuffer = MultiBuffer::new(ReadWrite);
19581        multibuffer.push_excerpts(
19582            buffer_1.clone(),
19583            [
19584                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19585                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19586                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19587            ],
19588            cx,
19589        );
19590        multibuffer.push_excerpts(
19591            buffer_2.clone(),
19592            [
19593                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19594                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19595                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19596            ],
19597            cx,
19598        );
19599        multibuffer.push_excerpts(
19600            buffer_3.clone(),
19601            [
19602                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19603                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19604                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19605            ],
19606            cx,
19607        );
19608        multibuffer
19609    });
19610
19611    let editor =
19612        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19613    editor
19614        .update(cx, |editor, _window, cx| {
19615            for (buffer, diff_base) in [
19616                (buffer_1.clone(), file_1_old),
19617                (buffer_2.clone(), file_2_old),
19618                (buffer_3.clone(), file_3_old),
19619            ] {
19620                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
19621                editor
19622                    .buffer
19623                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
19624            }
19625        })
19626        .unwrap();
19627
19628    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19629    cx.run_until_parked();
19630
19631    cx.assert_editor_state(
19632        &"
19633            ˇaaa
19634            ccc
19635            ddd
19636
19637            ggg
19638            hhh
19639
19640
19641            lll
19642            mmm
19643            NNN
19644
19645            qqq
19646            rrr
19647
19648            uuu
19649            111
19650            222
19651            333
19652
19653            666
19654            777
19655
19656            000
19657            !!!"
19658        .unindent(),
19659    );
19660
19661    cx.update_editor(|editor, window, cx| {
19662        editor.select_all(&SelectAll, window, cx);
19663        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19664    });
19665    cx.executor().run_until_parked();
19666
19667    cx.assert_state_with_diff(
19668        "
19669            «aaa
19670          - bbb
19671            ccc
19672            ddd
19673
19674            ggg
19675            hhh
19676
19677
19678            lll
19679            mmm
19680          - nnn
19681          + NNN
19682
19683            qqq
19684            rrr
19685
19686            uuu
19687            111
19688            222
19689            333
19690
19691          + 666
19692            777
19693
19694            000
19695            !!!ˇ»"
19696            .unindent(),
19697    );
19698}
19699
19700#[gpui::test]
19701async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
19702    init_test(cx, |_| {});
19703
19704    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
19705    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
19706
19707    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
19708    let multi_buffer = cx.new(|cx| {
19709        let mut multibuffer = MultiBuffer::new(ReadWrite);
19710        multibuffer.push_excerpts(
19711            buffer.clone(),
19712            [
19713                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
19714                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
19715                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
19716            ],
19717            cx,
19718        );
19719        multibuffer
19720    });
19721
19722    let editor =
19723        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19724    editor
19725        .update(cx, |editor, _window, cx| {
19726            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
19727            editor
19728                .buffer
19729                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
19730        })
19731        .unwrap();
19732
19733    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19734    cx.run_until_parked();
19735
19736    cx.update_editor(|editor, window, cx| {
19737        editor.expand_all_diff_hunks(&Default::default(), window, cx)
19738    });
19739    cx.executor().run_until_parked();
19740
19741    // When the start of a hunk coincides with the start of its excerpt,
19742    // the hunk is expanded. When the start of a hunk is earlier than
19743    // the start of its excerpt, the hunk is not expanded.
19744    cx.assert_state_with_diff(
19745        "
19746            ˇaaa
19747          - bbb
19748          + BBB
19749
19750          - ddd
19751          - eee
19752          + DDD
19753          + EEE
19754            fff
19755
19756            iii
19757        "
19758        .unindent(),
19759    );
19760}
19761
19762#[gpui::test]
19763async fn test_edits_around_expanded_insertion_hunks(
19764    executor: BackgroundExecutor,
19765    cx: &mut TestAppContext,
19766) {
19767    init_test(cx, |_| {});
19768
19769    let mut cx = EditorTestContext::new(cx).await;
19770
19771    let diff_base = r#"
19772        use some::mod1;
19773        use some::mod2;
19774
19775        const A: u32 = 42;
19776
19777        fn main() {
19778            println!("hello");
19779
19780            println!("world");
19781        }
19782        "#
19783    .unindent();
19784    executor.run_until_parked();
19785    cx.set_state(
19786        &r#"
19787        use some::mod1;
19788        use some::mod2;
19789
19790        const A: u32 = 42;
19791        const B: u32 = 42;
19792        const C: u32 = 42;
19793        ˇ
19794
19795        fn main() {
19796            println!("hello");
19797
19798            println!("world");
19799        }
19800        "#
19801        .unindent(),
19802    );
19803
19804    cx.set_head_text(&diff_base);
19805    executor.run_until_parked();
19806
19807    cx.update_editor(|editor, window, cx| {
19808        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19809    });
19810    executor.run_until_parked();
19811
19812    cx.assert_state_with_diff(
19813        r#"
19814        use some::mod1;
19815        use some::mod2;
19816
19817        const A: u32 = 42;
19818      + const B: u32 = 42;
19819      + const C: u32 = 42;
19820      + ˇ
19821
19822        fn main() {
19823            println!("hello");
19824
19825            println!("world");
19826        }
19827      "#
19828        .unindent(),
19829    );
19830
19831    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
19832    executor.run_until_parked();
19833
19834    cx.assert_state_with_diff(
19835        r#"
19836        use some::mod1;
19837        use some::mod2;
19838
19839        const A: u32 = 42;
19840      + const B: u32 = 42;
19841      + const C: u32 = 42;
19842      + const D: u32 = 42;
19843      + ˇ
19844
19845        fn main() {
19846            println!("hello");
19847
19848            println!("world");
19849        }
19850      "#
19851        .unindent(),
19852    );
19853
19854    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
19855    executor.run_until_parked();
19856
19857    cx.assert_state_with_diff(
19858        r#"
19859        use some::mod1;
19860        use some::mod2;
19861
19862        const A: u32 = 42;
19863      + const B: u32 = 42;
19864      + const C: u32 = 42;
19865      + const D: u32 = 42;
19866      + const E: u32 = 42;
19867      + ˇ
19868
19869        fn main() {
19870            println!("hello");
19871
19872            println!("world");
19873        }
19874      "#
19875        .unindent(),
19876    );
19877
19878    cx.update_editor(|editor, window, cx| {
19879        editor.delete_line(&DeleteLine, window, cx);
19880    });
19881    executor.run_until_parked();
19882
19883    cx.assert_state_with_diff(
19884        r#"
19885        use some::mod1;
19886        use some::mod2;
19887
19888        const A: u32 = 42;
19889      + const B: u32 = 42;
19890      + const C: u32 = 42;
19891      + const D: u32 = 42;
19892      + const E: u32 = 42;
19893        ˇ
19894        fn main() {
19895            println!("hello");
19896
19897            println!("world");
19898        }
19899      "#
19900        .unindent(),
19901    );
19902
19903    cx.update_editor(|editor, window, cx| {
19904        editor.move_up(&MoveUp, window, cx);
19905        editor.delete_line(&DeleteLine, window, cx);
19906        editor.move_up(&MoveUp, window, cx);
19907        editor.delete_line(&DeleteLine, window, cx);
19908        editor.move_up(&MoveUp, window, cx);
19909        editor.delete_line(&DeleteLine, window, cx);
19910    });
19911    executor.run_until_parked();
19912    cx.assert_state_with_diff(
19913        r#"
19914        use some::mod1;
19915        use some::mod2;
19916
19917        const A: u32 = 42;
19918      + const B: u32 = 42;
19919        ˇ
19920        fn main() {
19921            println!("hello");
19922
19923            println!("world");
19924        }
19925      "#
19926        .unindent(),
19927    );
19928
19929    cx.update_editor(|editor, window, cx| {
19930        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
19931        editor.delete_line(&DeleteLine, window, cx);
19932    });
19933    executor.run_until_parked();
19934    cx.assert_state_with_diff(
19935        r#"
19936        ˇ
19937        fn main() {
19938            println!("hello");
19939
19940            println!("world");
19941        }
19942      "#
19943        .unindent(),
19944    );
19945}
19946
19947#[gpui::test]
19948async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
19949    init_test(cx, |_| {});
19950
19951    let mut cx = EditorTestContext::new(cx).await;
19952    cx.set_head_text(indoc! { "
19953        one
19954        two
19955        three
19956        four
19957        five
19958        "
19959    });
19960    cx.set_state(indoc! { "
19961        one
19962        ˇthree
19963        five
19964    "});
19965    cx.run_until_parked();
19966    cx.update_editor(|editor, window, cx| {
19967        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19968    });
19969    cx.assert_state_with_diff(
19970        indoc! { "
19971        one
19972      - two
19973        ˇthree
19974      - four
19975        five
19976    "}
19977        .to_string(),
19978    );
19979    cx.update_editor(|editor, window, cx| {
19980        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19981    });
19982
19983    cx.assert_state_with_diff(
19984        indoc! { "
19985        one
19986        ˇthree
19987        five
19988    "}
19989        .to_string(),
19990    );
19991
19992    cx.set_state(indoc! { "
19993        one
19994        ˇTWO
19995        three
19996        four
19997        five
19998    "});
19999    cx.run_until_parked();
20000    cx.update_editor(|editor, window, cx| {
20001        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20002    });
20003
20004    cx.assert_state_with_diff(
20005        indoc! { "
20006            one
20007          - two
20008          + ˇTWO
20009            three
20010            four
20011            five
20012        "}
20013        .to_string(),
20014    );
20015    cx.update_editor(|editor, window, cx| {
20016        editor.move_up(&Default::default(), window, cx);
20017        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20018    });
20019    cx.assert_state_with_diff(
20020        indoc! { "
20021            one
20022            ˇTWO
20023            three
20024            four
20025            five
20026        "}
20027        .to_string(),
20028    );
20029}
20030
20031#[gpui::test]
20032async fn test_edits_around_expanded_deletion_hunks(
20033    executor: BackgroundExecutor,
20034    cx: &mut TestAppContext,
20035) {
20036    init_test(cx, |_| {});
20037
20038    let mut cx = EditorTestContext::new(cx).await;
20039
20040    let diff_base = r#"
20041        use some::mod1;
20042        use some::mod2;
20043
20044        const A: u32 = 42;
20045        const B: u32 = 42;
20046        const C: u32 = 42;
20047
20048
20049        fn main() {
20050            println!("hello");
20051
20052            println!("world");
20053        }
20054    "#
20055    .unindent();
20056    executor.run_until_parked();
20057    cx.set_state(
20058        &r#"
20059        use some::mod1;
20060        use some::mod2;
20061
20062        ˇconst B: u32 = 42;
20063        const C: u32 = 42;
20064
20065
20066        fn main() {
20067            println!("hello");
20068
20069            println!("world");
20070        }
20071        "#
20072        .unindent(),
20073    );
20074
20075    cx.set_head_text(&diff_base);
20076    executor.run_until_parked();
20077
20078    cx.update_editor(|editor, window, cx| {
20079        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20080    });
20081    executor.run_until_parked();
20082
20083    cx.assert_state_with_diff(
20084        r#"
20085        use some::mod1;
20086        use some::mod2;
20087
20088      - const A: u32 = 42;
20089        ˇconst B: u32 = 42;
20090        const C: u32 = 42;
20091
20092
20093        fn main() {
20094            println!("hello");
20095
20096            println!("world");
20097        }
20098      "#
20099        .unindent(),
20100    );
20101
20102    cx.update_editor(|editor, window, cx| {
20103        editor.delete_line(&DeleteLine, window, cx);
20104    });
20105    executor.run_until_parked();
20106    cx.assert_state_with_diff(
20107        r#"
20108        use some::mod1;
20109        use some::mod2;
20110
20111      - const A: u32 = 42;
20112      - const B: u32 = 42;
20113        ˇconst C: u32 = 42;
20114
20115
20116        fn main() {
20117            println!("hello");
20118
20119            println!("world");
20120        }
20121      "#
20122        .unindent(),
20123    );
20124
20125    cx.update_editor(|editor, window, cx| {
20126        editor.delete_line(&DeleteLine, window, cx);
20127    });
20128    executor.run_until_parked();
20129    cx.assert_state_with_diff(
20130        r#"
20131        use some::mod1;
20132        use some::mod2;
20133
20134      - const A: u32 = 42;
20135      - const B: u32 = 42;
20136      - const C: u32 = 42;
20137        ˇ
20138
20139        fn main() {
20140            println!("hello");
20141
20142            println!("world");
20143        }
20144      "#
20145        .unindent(),
20146    );
20147
20148    cx.update_editor(|editor, window, cx| {
20149        editor.handle_input("replacement", window, cx);
20150    });
20151    executor.run_until_parked();
20152    cx.assert_state_with_diff(
20153        r#"
20154        use some::mod1;
20155        use some::mod2;
20156
20157      - const A: u32 = 42;
20158      - const B: u32 = 42;
20159      - const C: u32 = 42;
20160      -
20161      + replacementˇ
20162
20163        fn main() {
20164            println!("hello");
20165
20166            println!("world");
20167        }
20168      "#
20169        .unindent(),
20170    );
20171}
20172
20173#[gpui::test]
20174async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20175    init_test(cx, |_| {});
20176
20177    let mut cx = EditorTestContext::new(cx).await;
20178
20179    let base_text = r#"
20180        one
20181        two
20182        three
20183        four
20184        five
20185    "#
20186    .unindent();
20187    executor.run_until_parked();
20188    cx.set_state(
20189        &r#"
20190        one
20191        two
20192        fˇour
20193        five
20194        "#
20195        .unindent(),
20196    );
20197
20198    cx.set_head_text(&base_text);
20199    executor.run_until_parked();
20200
20201    cx.update_editor(|editor, window, cx| {
20202        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20203    });
20204    executor.run_until_parked();
20205
20206    cx.assert_state_with_diff(
20207        r#"
20208          one
20209          two
20210        - three
20211          fˇour
20212          five
20213        "#
20214        .unindent(),
20215    );
20216
20217    cx.update_editor(|editor, window, cx| {
20218        editor.backspace(&Backspace, window, cx);
20219        editor.backspace(&Backspace, window, cx);
20220    });
20221    executor.run_until_parked();
20222    cx.assert_state_with_diff(
20223        r#"
20224          one
20225          two
20226        - threeˇ
20227        - four
20228        + our
20229          five
20230        "#
20231        .unindent(),
20232    );
20233}
20234
20235#[gpui::test]
20236async fn test_edit_after_expanded_modification_hunk(
20237    executor: BackgroundExecutor,
20238    cx: &mut TestAppContext,
20239) {
20240    init_test(cx, |_| {});
20241
20242    let mut cx = EditorTestContext::new(cx).await;
20243
20244    let diff_base = r#"
20245        use some::mod1;
20246        use some::mod2;
20247
20248        const A: u32 = 42;
20249        const B: u32 = 42;
20250        const C: u32 = 42;
20251        const D: u32 = 42;
20252
20253
20254        fn main() {
20255            println!("hello");
20256
20257            println!("world");
20258        }"#
20259    .unindent();
20260
20261    cx.set_state(
20262        &r#"
20263        use some::mod1;
20264        use some::mod2;
20265
20266        const A: u32 = 42;
20267        const B: u32 = 42;
20268        const C: u32 = 43ˇ
20269        const D: u32 = 42;
20270
20271
20272        fn main() {
20273            println!("hello");
20274
20275            println!("world");
20276        }"#
20277        .unindent(),
20278    );
20279
20280    cx.set_head_text(&diff_base);
20281    executor.run_until_parked();
20282    cx.update_editor(|editor, window, cx| {
20283        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20284    });
20285    executor.run_until_parked();
20286
20287    cx.assert_state_with_diff(
20288        r#"
20289        use some::mod1;
20290        use some::mod2;
20291
20292        const A: u32 = 42;
20293        const B: u32 = 42;
20294      - const C: u32 = 42;
20295      + const C: u32 = 43ˇ
20296        const D: u32 = 42;
20297
20298
20299        fn main() {
20300            println!("hello");
20301
20302            println!("world");
20303        }"#
20304        .unindent(),
20305    );
20306
20307    cx.update_editor(|editor, window, cx| {
20308        editor.handle_input("\nnew_line\n", window, cx);
20309    });
20310    executor.run_until_parked();
20311
20312    cx.assert_state_with_diff(
20313        r#"
20314        use some::mod1;
20315        use some::mod2;
20316
20317        const A: u32 = 42;
20318        const B: u32 = 42;
20319      - const C: u32 = 42;
20320      + const C: u32 = 43
20321      + new_line
20322      + ˇ
20323        const D: u32 = 42;
20324
20325
20326        fn main() {
20327            println!("hello");
20328
20329            println!("world");
20330        }"#
20331        .unindent(),
20332    );
20333}
20334
20335#[gpui::test]
20336async fn test_stage_and_unstage_added_file_hunk(
20337    executor: BackgroundExecutor,
20338    cx: &mut TestAppContext,
20339) {
20340    init_test(cx, |_| {});
20341
20342    let mut cx = EditorTestContext::new(cx).await;
20343    cx.update_editor(|editor, _, cx| {
20344        editor.set_expand_all_diff_hunks(cx);
20345    });
20346
20347    let working_copy = r#"
20348            ˇfn main() {
20349                println!("hello, world!");
20350            }
20351        "#
20352    .unindent();
20353
20354    cx.set_state(&working_copy);
20355    executor.run_until_parked();
20356
20357    cx.assert_state_with_diff(
20358        r#"
20359            + ˇfn main() {
20360            +     println!("hello, world!");
20361            + }
20362        "#
20363        .unindent(),
20364    );
20365    cx.assert_index_text(None);
20366
20367    cx.update_editor(|editor, window, cx| {
20368        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20369    });
20370    executor.run_until_parked();
20371    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
20372    cx.assert_state_with_diff(
20373        r#"
20374            + ˇfn main() {
20375            +     println!("hello, world!");
20376            + }
20377        "#
20378        .unindent(),
20379    );
20380
20381    cx.update_editor(|editor, window, cx| {
20382        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20383    });
20384    executor.run_until_parked();
20385    cx.assert_index_text(None);
20386}
20387
20388async fn setup_indent_guides_editor(
20389    text: &str,
20390    cx: &mut TestAppContext,
20391) -> (BufferId, EditorTestContext) {
20392    init_test(cx, |_| {});
20393
20394    let mut cx = EditorTestContext::new(cx).await;
20395
20396    let buffer_id = cx.update_editor(|editor, window, cx| {
20397        editor.set_text(text, window, cx);
20398        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
20399
20400        buffer_ids[0]
20401    });
20402
20403    (buffer_id, cx)
20404}
20405
20406fn assert_indent_guides(
20407    range: Range<u32>,
20408    expected: Vec<IndentGuide>,
20409    active_indices: Option<Vec<usize>>,
20410    cx: &mut EditorTestContext,
20411) {
20412    let indent_guides = cx.update_editor(|editor, window, cx| {
20413        let snapshot = editor.snapshot(window, cx).display_snapshot;
20414        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
20415            editor,
20416            MultiBufferRow(range.start)..MultiBufferRow(range.end),
20417            true,
20418            &snapshot,
20419            cx,
20420        );
20421
20422        indent_guides.sort_by(|a, b| {
20423            a.depth.cmp(&b.depth).then(
20424                a.start_row
20425                    .cmp(&b.start_row)
20426                    .then(a.end_row.cmp(&b.end_row)),
20427            )
20428        });
20429        indent_guides
20430    });
20431
20432    if let Some(expected) = active_indices {
20433        let active_indices = cx.update_editor(|editor, window, cx| {
20434            let snapshot = editor.snapshot(window, cx).display_snapshot;
20435            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
20436        });
20437
20438        assert_eq!(
20439            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
20440            expected,
20441            "Active indent guide indices do not match"
20442        );
20443    }
20444
20445    assert_eq!(indent_guides, expected, "Indent guides do not match");
20446}
20447
20448fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
20449    IndentGuide {
20450        buffer_id,
20451        start_row: MultiBufferRow(start_row),
20452        end_row: MultiBufferRow(end_row),
20453        depth,
20454        tab_size: 4,
20455        settings: IndentGuideSettings {
20456            enabled: true,
20457            line_width: 1,
20458            active_line_width: 1,
20459            coloring: IndentGuideColoring::default(),
20460            background_coloring: IndentGuideBackgroundColoring::default(),
20461        },
20462    }
20463}
20464
20465#[gpui::test]
20466async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
20467    let (buffer_id, mut cx) = setup_indent_guides_editor(
20468        &"
20469        fn main() {
20470            let a = 1;
20471        }"
20472        .unindent(),
20473        cx,
20474    )
20475    .await;
20476
20477    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20478}
20479
20480#[gpui::test]
20481async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
20482    let (buffer_id, mut cx) = setup_indent_guides_editor(
20483        &"
20484        fn main() {
20485            let a = 1;
20486            let b = 2;
20487        }"
20488        .unindent(),
20489        cx,
20490    )
20491    .await;
20492
20493    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
20494}
20495
20496#[gpui::test]
20497async fn test_indent_guide_nested(cx: &mut TestAppContext) {
20498    let (buffer_id, mut cx) = setup_indent_guides_editor(
20499        &"
20500        fn main() {
20501            let a = 1;
20502            if a == 3 {
20503                let b = 2;
20504            } else {
20505                let c = 3;
20506            }
20507        }"
20508        .unindent(),
20509        cx,
20510    )
20511    .await;
20512
20513    assert_indent_guides(
20514        0..8,
20515        vec![
20516            indent_guide(buffer_id, 1, 6, 0),
20517            indent_guide(buffer_id, 3, 3, 1),
20518            indent_guide(buffer_id, 5, 5, 1),
20519        ],
20520        None,
20521        &mut cx,
20522    );
20523}
20524
20525#[gpui::test]
20526async fn test_indent_guide_tab(cx: &mut TestAppContext) {
20527    let (buffer_id, mut cx) = setup_indent_guides_editor(
20528        &"
20529        fn main() {
20530            let a = 1;
20531                let b = 2;
20532            let c = 3;
20533        }"
20534        .unindent(),
20535        cx,
20536    )
20537    .await;
20538
20539    assert_indent_guides(
20540        0..5,
20541        vec![
20542            indent_guide(buffer_id, 1, 3, 0),
20543            indent_guide(buffer_id, 2, 2, 1),
20544        ],
20545        None,
20546        &mut cx,
20547    );
20548}
20549
20550#[gpui::test]
20551async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
20552    let (buffer_id, mut cx) = setup_indent_guides_editor(
20553        &"
20554        fn main() {
20555            let a = 1;
20556
20557            let c = 3;
20558        }"
20559        .unindent(),
20560        cx,
20561    )
20562    .await;
20563
20564    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
20565}
20566
20567#[gpui::test]
20568async fn test_indent_guide_complex(cx: &mut TestAppContext) {
20569    let (buffer_id, mut cx) = setup_indent_guides_editor(
20570        &"
20571        fn main() {
20572            let a = 1;
20573
20574            let c = 3;
20575
20576            if a == 3 {
20577                let b = 2;
20578            } else {
20579                let c = 3;
20580            }
20581        }"
20582        .unindent(),
20583        cx,
20584    )
20585    .await;
20586
20587    assert_indent_guides(
20588        0..11,
20589        vec![
20590            indent_guide(buffer_id, 1, 9, 0),
20591            indent_guide(buffer_id, 6, 6, 1),
20592            indent_guide(buffer_id, 8, 8, 1),
20593        ],
20594        None,
20595        &mut cx,
20596    );
20597}
20598
20599#[gpui::test]
20600async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
20601    let (buffer_id, mut cx) = setup_indent_guides_editor(
20602        &"
20603        fn main() {
20604            let a = 1;
20605
20606            let c = 3;
20607
20608            if a == 3 {
20609                let b = 2;
20610            } else {
20611                let c = 3;
20612            }
20613        }"
20614        .unindent(),
20615        cx,
20616    )
20617    .await;
20618
20619    assert_indent_guides(
20620        1..11,
20621        vec![
20622            indent_guide(buffer_id, 1, 9, 0),
20623            indent_guide(buffer_id, 6, 6, 1),
20624            indent_guide(buffer_id, 8, 8, 1),
20625        ],
20626        None,
20627        &mut cx,
20628    );
20629}
20630
20631#[gpui::test]
20632async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
20633    let (buffer_id, mut cx) = setup_indent_guides_editor(
20634        &"
20635        fn main() {
20636            let a = 1;
20637
20638            let c = 3;
20639
20640            if a == 3 {
20641                let b = 2;
20642            } else {
20643                let c = 3;
20644            }
20645        }"
20646        .unindent(),
20647        cx,
20648    )
20649    .await;
20650
20651    assert_indent_guides(
20652        1..10,
20653        vec![
20654            indent_guide(buffer_id, 1, 9, 0),
20655            indent_guide(buffer_id, 6, 6, 1),
20656            indent_guide(buffer_id, 8, 8, 1),
20657        ],
20658        None,
20659        &mut cx,
20660    );
20661}
20662
20663#[gpui::test]
20664async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
20665    let (buffer_id, mut cx) = setup_indent_guides_editor(
20666        &"
20667        fn main() {
20668            if a {
20669                b(
20670                    c,
20671                    d,
20672                )
20673            } else {
20674                e(
20675                    f
20676                )
20677            }
20678        }"
20679        .unindent(),
20680        cx,
20681    )
20682    .await;
20683
20684    assert_indent_guides(
20685        0..11,
20686        vec![
20687            indent_guide(buffer_id, 1, 10, 0),
20688            indent_guide(buffer_id, 2, 5, 1),
20689            indent_guide(buffer_id, 7, 9, 1),
20690            indent_guide(buffer_id, 3, 4, 2),
20691            indent_guide(buffer_id, 8, 8, 2),
20692        ],
20693        None,
20694        &mut cx,
20695    );
20696
20697    cx.update_editor(|editor, window, cx| {
20698        editor.fold_at(MultiBufferRow(2), window, cx);
20699        assert_eq!(
20700            editor.display_text(cx),
20701            "
20702            fn main() {
20703                if a {
20704                    b(⋯
20705                    )
20706                } else {
20707                    e(
20708                        f
20709                    )
20710                }
20711            }"
20712            .unindent()
20713        );
20714    });
20715
20716    assert_indent_guides(
20717        0..11,
20718        vec![
20719            indent_guide(buffer_id, 1, 10, 0),
20720            indent_guide(buffer_id, 2, 5, 1),
20721            indent_guide(buffer_id, 7, 9, 1),
20722            indent_guide(buffer_id, 8, 8, 2),
20723        ],
20724        None,
20725        &mut cx,
20726    );
20727}
20728
20729#[gpui::test]
20730async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
20731    let (buffer_id, mut cx) = setup_indent_guides_editor(
20732        &"
20733        block1
20734            block2
20735                block3
20736                    block4
20737            block2
20738        block1
20739        block1"
20740            .unindent(),
20741        cx,
20742    )
20743    .await;
20744
20745    assert_indent_guides(
20746        1..10,
20747        vec![
20748            indent_guide(buffer_id, 1, 4, 0),
20749            indent_guide(buffer_id, 2, 3, 1),
20750            indent_guide(buffer_id, 3, 3, 2),
20751        ],
20752        None,
20753        &mut cx,
20754    );
20755}
20756
20757#[gpui::test]
20758async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
20759    let (buffer_id, mut cx) = setup_indent_guides_editor(
20760        &"
20761        block1
20762            block2
20763                block3
20764
20765        block1
20766        block1"
20767            .unindent(),
20768        cx,
20769    )
20770    .await;
20771
20772    assert_indent_guides(
20773        0..6,
20774        vec![
20775            indent_guide(buffer_id, 1, 2, 0),
20776            indent_guide(buffer_id, 2, 2, 1),
20777        ],
20778        None,
20779        &mut cx,
20780    );
20781}
20782
20783#[gpui::test]
20784async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
20785    let (buffer_id, mut cx) = setup_indent_guides_editor(
20786        &"
20787        function component() {
20788        \treturn (
20789        \t\t\t
20790        \t\t<div>
20791        \t\t\t<abc></abc>
20792        \t\t</div>
20793        \t)
20794        }"
20795        .unindent(),
20796        cx,
20797    )
20798    .await;
20799
20800    assert_indent_guides(
20801        0..8,
20802        vec![
20803            indent_guide(buffer_id, 1, 6, 0),
20804            indent_guide(buffer_id, 2, 5, 1),
20805            indent_guide(buffer_id, 4, 4, 2),
20806        ],
20807        None,
20808        &mut cx,
20809    );
20810}
20811
20812#[gpui::test]
20813async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
20814    let (buffer_id, mut cx) = setup_indent_guides_editor(
20815        &"
20816        function component() {
20817        \treturn (
20818        \t
20819        \t\t<div>
20820        \t\t\t<abc></abc>
20821        \t\t</div>
20822        \t)
20823        }"
20824        .unindent(),
20825        cx,
20826    )
20827    .await;
20828
20829    assert_indent_guides(
20830        0..8,
20831        vec![
20832            indent_guide(buffer_id, 1, 6, 0),
20833            indent_guide(buffer_id, 2, 5, 1),
20834            indent_guide(buffer_id, 4, 4, 2),
20835        ],
20836        None,
20837        &mut cx,
20838    );
20839}
20840
20841#[gpui::test]
20842async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
20843    let (buffer_id, mut cx) = setup_indent_guides_editor(
20844        &"
20845        block1
20846
20847
20848
20849            block2
20850        "
20851        .unindent(),
20852        cx,
20853    )
20854    .await;
20855
20856    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20857}
20858
20859#[gpui::test]
20860async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
20861    let (buffer_id, mut cx) = setup_indent_guides_editor(
20862        &"
20863        def a:
20864        \tb = 3
20865        \tif True:
20866        \t\tc = 4
20867        \t\td = 5
20868        \tprint(b)
20869        "
20870        .unindent(),
20871        cx,
20872    )
20873    .await;
20874
20875    assert_indent_guides(
20876        0..6,
20877        vec![
20878            indent_guide(buffer_id, 1, 5, 0),
20879            indent_guide(buffer_id, 3, 4, 1),
20880        ],
20881        None,
20882        &mut cx,
20883    );
20884}
20885
20886#[gpui::test]
20887async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
20888    let (buffer_id, mut cx) = setup_indent_guides_editor(
20889        &"
20890    fn main() {
20891        let a = 1;
20892    }"
20893        .unindent(),
20894        cx,
20895    )
20896    .await;
20897
20898    cx.update_editor(|editor, window, cx| {
20899        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20900            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20901        });
20902    });
20903
20904    assert_indent_guides(
20905        0..3,
20906        vec![indent_guide(buffer_id, 1, 1, 0)],
20907        Some(vec![0]),
20908        &mut cx,
20909    );
20910}
20911
20912#[gpui::test]
20913async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
20914    let (buffer_id, mut cx) = setup_indent_guides_editor(
20915        &"
20916    fn main() {
20917        if 1 == 2 {
20918            let a = 1;
20919        }
20920    }"
20921        .unindent(),
20922        cx,
20923    )
20924    .await;
20925
20926    cx.update_editor(|editor, window, cx| {
20927        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20928            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20929        });
20930    });
20931
20932    assert_indent_guides(
20933        0..4,
20934        vec![
20935            indent_guide(buffer_id, 1, 3, 0),
20936            indent_guide(buffer_id, 2, 2, 1),
20937        ],
20938        Some(vec![1]),
20939        &mut cx,
20940    );
20941
20942    cx.update_editor(|editor, window, cx| {
20943        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20944            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20945        });
20946    });
20947
20948    assert_indent_guides(
20949        0..4,
20950        vec![
20951            indent_guide(buffer_id, 1, 3, 0),
20952            indent_guide(buffer_id, 2, 2, 1),
20953        ],
20954        Some(vec![1]),
20955        &mut cx,
20956    );
20957
20958    cx.update_editor(|editor, window, cx| {
20959        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20960            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
20961        });
20962    });
20963
20964    assert_indent_guides(
20965        0..4,
20966        vec![
20967            indent_guide(buffer_id, 1, 3, 0),
20968            indent_guide(buffer_id, 2, 2, 1),
20969        ],
20970        Some(vec![0]),
20971        &mut cx,
20972    );
20973}
20974
20975#[gpui::test]
20976async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
20977    let (buffer_id, mut cx) = setup_indent_guides_editor(
20978        &"
20979    fn main() {
20980        let a = 1;
20981
20982        let b = 2;
20983    }"
20984        .unindent(),
20985        cx,
20986    )
20987    .await;
20988
20989    cx.update_editor(|editor, window, cx| {
20990        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20991            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20992        });
20993    });
20994
20995    assert_indent_guides(
20996        0..5,
20997        vec![indent_guide(buffer_id, 1, 3, 0)],
20998        Some(vec![0]),
20999        &mut cx,
21000    );
21001}
21002
21003#[gpui::test]
21004async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
21005    let (buffer_id, mut cx) = setup_indent_guides_editor(
21006        &"
21007    def m:
21008        a = 1
21009        pass"
21010            .unindent(),
21011        cx,
21012    )
21013    .await;
21014
21015    cx.update_editor(|editor, window, cx| {
21016        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21017            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
21018        });
21019    });
21020
21021    assert_indent_guides(
21022        0..3,
21023        vec![indent_guide(buffer_id, 1, 2, 0)],
21024        Some(vec![0]),
21025        &mut cx,
21026    );
21027}
21028
21029#[gpui::test]
21030async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
21031    init_test(cx, |_| {});
21032    let mut cx = EditorTestContext::new(cx).await;
21033    let text = indoc! {
21034        "
21035        impl A {
21036            fn b() {
21037                0;
21038                3;
21039                5;
21040                6;
21041                7;
21042            }
21043        }
21044        "
21045    };
21046    let base_text = indoc! {
21047        "
21048        impl A {
21049            fn b() {
21050                0;
21051                1;
21052                2;
21053                3;
21054                4;
21055            }
21056            fn c() {
21057                5;
21058                6;
21059                7;
21060            }
21061        }
21062        "
21063    };
21064
21065    cx.update_editor(|editor, window, cx| {
21066        editor.set_text(text, window, cx);
21067
21068        editor.buffer().update(cx, |multibuffer, cx| {
21069            let buffer = multibuffer.as_singleton().unwrap();
21070            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
21071
21072            multibuffer.set_all_diff_hunks_expanded(cx);
21073            multibuffer.add_diff(diff, cx);
21074
21075            buffer.read(cx).remote_id()
21076        })
21077    });
21078    cx.run_until_parked();
21079
21080    cx.assert_state_with_diff(
21081        indoc! { "
21082          impl A {
21083              fn b() {
21084                  0;
21085        -         1;
21086        -         2;
21087                  3;
21088        -         4;
21089        -     }
21090        -     fn c() {
21091                  5;
21092                  6;
21093                  7;
21094              }
21095          }
21096          ˇ"
21097        }
21098        .to_string(),
21099    );
21100
21101    let mut actual_guides = cx.update_editor(|editor, window, cx| {
21102        editor
21103            .snapshot(window, cx)
21104            .buffer_snapshot()
21105            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
21106            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
21107            .collect::<Vec<_>>()
21108    });
21109    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
21110    assert_eq!(
21111        actual_guides,
21112        vec![
21113            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
21114            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
21115            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
21116        ]
21117    );
21118}
21119
21120#[gpui::test]
21121async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
21122    init_test(cx, |_| {});
21123    let mut cx = EditorTestContext::new(cx).await;
21124
21125    let diff_base = r#"
21126        a
21127        b
21128        c
21129        "#
21130    .unindent();
21131
21132    cx.set_state(
21133        &r#"
21134        ˇA
21135        b
21136        C
21137        "#
21138        .unindent(),
21139    );
21140    cx.set_head_text(&diff_base);
21141    cx.update_editor(|editor, window, cx| {
21142        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21143    });
21144    executor.run_until_parked();
21145
21146    let both_hunks_expanded = r#"
21147        - a
21148        + ˇA
21149          b
21150        - c
21151        + C
21152        "#
21153    .unindent();
21154
21155    cx.assert_state_with_diff(both_hunks_expanded.clone());
21156
21157    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21158        let snapshot = editor.snapshot(window, cx);
21159        let hunks = editor
21160            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21161            .collect::<Vec<_>>();
21162        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21163        let buffer_id = hunks[0].buffer_id;
21164        hunks
21165            .into_iter()
21166            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
21167            .collect::<Vec<_>>()
21168    });
21169    assert_eq!(hunk_ranges.len(), 2);
21170
21171    cx.update_editor(|editor, _, cx| {
21172        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21173    });
21174    executor.run_until_parked();
21175
21176    let second_hunk_expanded = r#"
21177          ˇA
21178          b
21179        - c
21180        + C
21181        "#
21182    .unindent();
21183
21184    cx.assert_state_with_diff(second_hunk_expanded);
21185
21186    cx.update_editor(|editor, _, cx| {
21187        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21188    });
21189    executor.run_until_parked();
21190
21191    cx.assert_state_with_diff(both_hunks_expanded.clone());
21192
21193    cx.update_editor(|editor, _, cx| {
21194        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21195    });
21196    executor.run_until_parked();
21197
21198    let first_hunk_expanded = r#"
21199        - a
21200        + ˇA
21201          b
21202          C
21203        "#
21204    .unindent();
21205
21206    cx.assert_state_with_diff(first_hunk_expanded);
21207
21208    cx.update_editor(|editor, _, cx| {
21209        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21210    });
21211    executor.run_until_parked();
21212
21213    cx.assert_state_with_diff(both_hunks_expanded);
21214
21215    cx.set_state(
21216        &r#"
21217        ˇA
21218        b
21219        "#
21220        .unindent(),
21221    );
21222    cx.run_until_parked();
21223
21224    // TODO this cursor position seems bad
21225    cx.assert_state_with_diff(
21226        r#"
21227        - ˇa
21228        + A
21229          b
21230        "#
21231        .unindent(),
21232    );
21233
21234    cx.update_editor(|editor, window, cx| {
21235        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21236    });
21237
21238    cx.assert_state_with_diff(
21239        r#"
21240            - ˇa
21241            + A
21242              b
21243            - c
21244            "#
21245        .unindent(),
21246    );
21247
21248    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21249        let snapshot = editor.snapshot(window, cx);
21250        let hunks = editor
21251            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21252            .collect::<Vec<_>>();
21253        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21254        let buffer_id = hunks[0].buffer_id;
21255        hunks
21256            .into_iter()
21257            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
21258            .collect::<Vec<_>>()
21259    });
21260    assert_eq!(hunk_ranges.len(), 2);
21261
21262    cx.update_editor(|editor, _, cx| {
21263        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21264    });
21265    executor.run_until_parked();
21266
21267    cx.assert_state_with_diff(
21268        r#"
21269        - ˇa
21270        + A
21271          b
21272        "#
21273        .unindent(),
21274    );
21275}
21276
21277#[gpui::test]
21278async fn test_toggle_deletion_hunk_at_start_of_file(
21279    executor: BackgroundExecutor,
21280    cx: &mut TestAppContext,
21281) {
21282    init_test(cx, |_| {});
21283    let mut cx = EditorTestContext::new(cx).await;
21284
21285    let diff_base = r#"
21286        a
21287        b
21288        c
21289        "#
21290    .unindent();
21291
21292    cx.set_state(
21293        &r#"
21294        ˇb
21295        c
21296        "#
21297        .unindent(),
21298    );
21299    cx.set_head_text(&diff_base);
21300    cx.update_editor(|editor, window, cx| {
21301        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21302    });
21303    executor.run_until_parked();
21304
21305    let hunk_expanded = r#"
21306        - a
21307          ˇb
21308          c
21309        "#
21310    .unindent();
21311
21312    cx.assert_state_with_diff(hunk_expanded.clone());
21313
21314    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21315        let snapshot = editor.snapshot(window, cx);
21316        let hunks = editor
21317            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21318            .collect::<Vec<_>>();
21319        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21320        let buffer_id = hunks[0].buffer_id;
21321        hunks
21322            .into_iter()
21323            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
21324            .collect::<Vec<_>>()
21325    });
21326    assert_eq!(hunk_ranges.len(), 1);
21327
21328    cx.update_editor(|editor, _, cx| {
21329        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21330    });
21331    executor.run_until_parked();
21332
21333    let hunk_collapsed = r#"
21334          ˇb
21335          c
21336        "#
21337    .unindent();
21338
21339    cx.assert_state_with_diff(hunk_collapsed);
21340
21341    cx.update_editor(|editor, _, cx| {
21342        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21343    });
21344    executor.run_until_parked();
21345
21346    cx.assert_state_with_diff(hunk_expanded);
21347}
21348
21349#[gpui::test]
21350async fn test_display_diff_hunks(cx: &mut TestAppContext) {
21351    init_test(cx, |_| {});
21352
21353    let fs = FakeFs::new(cx.executor());
21354    fs.insert_tree(
21355        path!("/test"),
21356        json!({
21357            ".git": {},
21358            "file-1": "ONE\n",
21359            "file-2": "TWO\n",
21360            "file-3": "THREE\n",
21361        }),
21362    )
21363    .await;
21364
21365    fs.set_head_for_repo(
21366        path!("/test/.git").as_ref(),
21367        &[
21368            ("file-1", "one\n".into()),
21369            ("file-2", "two\n".into()),
21370            ("file-3", "three\n".into()),
21371        ],
21372        "deadbeef",
21373    );
21374
21375    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
21376    let mut buffers = vec![];
21377    for i in 1..=3 {
21378        let buffer = project
21379            .update(cx, |project, cx| {
21380                let path = format!(path!("/test/file-{}"), i);
21381                project.open_local_buffer(path, cx)
21382            })
21383            .await
21384            .unwrap();
21385        buffers.push(buffer);
21386    }
21387
21388    let multibuffer = cx.new(|cx| {
21389        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
21390        multibuffer.set_all_diff_hunks_expanded(cx);
21391        for buffer in &buffers {
21392            let snapshot = buffer.read(cx).snapshot();
21393            multibuffer.set_excerpts_for_path(
21394                PathKey::with_sort_prefix(0, buffer.read(cx).file().unwrap().path().clone()),
21395                buffer.clone(),
21396                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
21397                2,
21398                cx,
21399            );
21400        }
21401        multibuffer
21402    });
21403
21404    let editor = cx.add_window(|window, cx| {
21405        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
21406    });
21407    cx.run_until_parked();
21408
21409    let snapshot = editor
21410        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21411        .unwrap();
21412    let hunks = snapshot
21413        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
21414        .map(|hunk| match hunk {
21415            DisplayDiffHunk::Unfolded {
21416                display_row_range, ..
21417            } => display_row_range,
21418            DisplayDiffHunk::Folded { .. } => unreachable!(),
21419        })
21420        .collect::<Vec<_>>();
21421    assert_eq!(
21422        hunks,
21423        [
21424            DisplayRow(2)..DisplayRow(4),
21425            DisplayRow(7)..DisplayRow(9),
21426            DisplayRow(12)..DisplayRow(14),
21427        ]
21428    );
21429}
21430
21431#[gpui::test]
21432async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
21433    init_test(cx, |_| {});
21434
21435    let mut cx = EditorTestContext::new(cx).await;
21436    cx.set_head_text(indoc! { "
21437        one
21438        two
21439        three
21440        four
21441        five
21442        "
21443    });
21444    cx.set_index_text(indoc! { "
21445        one
21446        two
21447        three
21448        four
21449        five
21450        "
21451    });
21452    cx.set_state(indoc! {"
21453        one
21454        TWO
21455        ˇTHREE
21456        FOUR
21457        five
21458    "});
21459    cx.run_until_parked();
21460    cx.update_editor(|editor, window, cx| {
21461        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21462    });
21463    cx.run_until_parked();
21464    cx.assert_index_text(Some(indoc! {"
21465        one
21466        TWO
21467        THREE
21468        FOUR
21469        five
21470    "}));
21471    cx.set_state(indoc! { "
21472        one
21473        TWO
21474        ˇTHREE-HUNDRED
21475        FOUR
21476        five
21477    "});
21478    cx.run_until_parked();
21479    cx.update_editor(|editor, window, cx| {
21480        let snapshot = editor.snapshot(window, cx);
21481        let hunks = editor
21482            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21483            .collect::<Vec<_>>();
21484        assert_eq!(hunks.len(), 1);
21485        assert_eq!(
21486            hunks[0].status(),
21487            DiffHunkStatus {
21488                kind: DiffHunkStatusKind::Modified,
21489                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
21490            }
21491        );
21492
21493        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21494    });
21495    cx.run_until_parked();
21496    cx.assert_index_text(Some(indoc! {"
21497        one
21498        TWO
21499        THREE-HUNDRED
21500        FOUR
21501        five
21502    "}));
21503}
21504
21505#[gpui::test]
21506fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
21507    init_test(cx, |_| {});
21508
21509    let editor = cx.add_window(|window, cx| {
21510        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
21511        build_editor(buffer, window, cx)
21512    });
21513
21514    let render_args = Arc::new(Mutex::new(None));
21515    let snapshot = editor
21516        .update(cx, |editor, window, cx| {
21517            let snapshot = editor.buffer().read(cx).snapshot(cx);
21518            let range =
21519                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
21520
21521            struct RenderArgs {
21522                row: MultiBufferRow,
21523                folded: bool,
21524                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
21525            }
21526
21527            let crease = Crease::inline(
21528                range,
21529                FoldPlaceholder::test(),
21530                {
21531                    let toggle_callback = render_args.clone();
21532                    move |row, folded, callback, _window, _cx| {
21533                        *toggle_callback.lock() = Some(RenderArgs {
21534                            row,
21535                            folded,
21536                            callback,
21537                        });
21538                        div()
21539                    }
21540                },
21541                |_row, _folded, _window, _cx| div(),
21542            );
21543
21544            editor.insert_creases(Some(crease), cx);
21545            let snapshot = editor.snapshot(window, cx);
21546            let _div =
21547                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
21548            snapshot
21549        })
21550        .unwrap();
21551
21552    let render_args = render_args.lock().take().unwrap();
21553    assert_eq!(render_args.row, MultiBufferRow(1));
21554    assert!(!render_args.folded);
21555    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21556
21557    cx.update_window(*editor, |_, window, cx| {
21558        (render_args.callback)(true, window, cx)
21559    })
21560    .unwrap();
21561    let snapshot = editor
21562        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21563        .unwrap();
21564    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
21565
21566    cx.update_window(*editor, |_, window, cx| {
21567        (render_args.callback)(false, window, cx)
21568    })
21569    .unwrap();
21570    let snapshot = editor
21571        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21572        .unwrap();
21573    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21574}
21575
21576#[gpui::test]
21577async fn test_input_text(cx: &mut TestAppContext) {
21578    init_test(cx, |_| {});
21579    let mut cx = EditorTestContext::new(cx).await;
21580
21581    cx.set_state(
21582        &r#"ˇone
21583        two
21584
21585        three
21586        fourˇ
21587        five
21588
21589        siˇx"#
21590            .unindent(),
21591    );
21592
21593    cx.dispatch_action(HandleInput(String::new()));
21594    cx.assert_editor_state(
21595        &r#"ˇone
21596        two
21597
21598        three
21599        fourˇ
21600        five
21601
21602        siˇx"#
21603            .unindent(),
21604    );
21605
21606    cx.dispatch_action(HandleInput("AAAA".to_string()));
21607    cx.assert_editor_state(
21608        &r#"AAAAˇone
21609        two
21610
21611        three
21612        fourAAAAˇ
21613        five
21614
21615        siAAAAˇx"#
21616            .unindent(),
21617    );
21618}
21619
21620#[gpui::test]
21621async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
21622    init_test(cx, |_| {});
21623
21624    let mut cx = EditorTestContext::new(cx).await;
21625    cx.set_state(
21626        r#"let foo = 1;
21627let foo = 2;
21628let foo = 3;
21629let fooˇ = 4;
21630let foo = 5;
21631let foo = 6;
21632let foo = 7;
21633let foo = 8;
21634let foo = 9;
21635let foo = 10;
21636let foo = 11;
21637let foo = 12;
21638let foo = 13;
21639let foo = 14;
21640let foo = 15;"#,
21641    );
21642
21643    cx.update_editor(|e, window, cx| {
21644        assert_eq!(
21645            e.next_scroll_position,
21646            NextScrollCursorCenterTopBottom::Center,
21647            "Default next scroll direction is center",
21648        );
21649
21650        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21651        assert_eq!(
21652            e.next_scroll_position,
21653            NextScrollCursorCenterTopBottom::Top,
21654            "After center, next scroll direction should be top",
21655        );
21656
21657        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21658        assert_eq!(
21659            e.next_scroll_position,
21660            NextScrollCursorCenterTopBottom::Bottom,
21661            "After top, next scroll direction should be bottom",
21662        );
21663
21664        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21665        assert_eq!(
21666            e.next_scroll_position,
21667            NextScrollCursorCenterTopBottom::Center,
21668            "After bottom, scrolling should start over",
21669        );
21670
21671        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21672        assert_eq!(
21673            e.next_scroll_position,
21674            NextScrollCursorCenterTopBottom::Top,
21675            "Scrolling continues if retriggered fast enough"
21676        );
21677    });
21678
21679    cx.executor()
21680        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
21681    cx.executor().run_until_parked();
21682    cx.update_editor(|e, _, _| {
21683        assert_eq!(
21684            e.next_scroll_position,
21685            NextScrollCursorCenterTopBottom::Center,
21686            "If scrolling is not triggered fast enough, it should reset"
21687        );
21688    });
21689}
21690
21691#[gpui::test]
21692async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
21693    init_test(cx, |_| {});
21694    let mut cx = EditorLspTestContext::new_rust(
21695        lsp::ServerCapabilities {
21696            definition_provider: Some(lsp::OneOf::Left(true)),
21697            references_provider: Some(lsp::OneOf::Left(true)),
21698            ..lsp::ServerCapabilities::default()
21699        },
21700        cx,
21701    )
21702    .await;
21703
21704    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
21705        let go_to_definition = cx
21706            .lsp
21707            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21708                move |params, _| async move {
21709                    if empty_go_to_definition {
21710                        Ok(None)
21711                    } else {
21712                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
21713                            uri: params.text_document_position_params.text_document.uri,
21714                            range: lsp::Range::new(
21715                                lsp::Position::new(4, 3),
21716                                lsp::Position::new(4, 6),
21717                            ),
21718                        })))
21719                    }
21720                },
21721            );
21722        let references = cx
21723            .lsp
21724            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21725                Ok(Some(vec![lsp::Location {
21726                    uri: params.text_document_position.text_document.uri,
21727                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
21728                }]))
21729            });
21730        (go_to_definition, references)
21731    };
21732
21733    cx.set_state(
21734        &r#"fn one() {
21735            let mut a = ˇtwo();
21736        }
21737
21738        fn two() {}"#
21739            .unindent(),
21740    );
21741    set_up_lsp_handlers(false, &mut cx);
21742    let navigated = cx
21743        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21744        .await
21745        .expect("Failed to navigate to definition");
21746    assert_eq!(
21747        navigated,
21748        Navigated::Yes,
21749        "Should have navigated to definition from the GetDefinition response"
21750    );
21751    cx.assert_editor_state(
21752        &r#"fn one() {
21753            let mut a = two();
21754        }
21755
21756        fn «twoˇ»() {}"#
21757            .unindent(),
21758    );
21759
21760    let editors = cx.update_workspace(|workspace, _, cx| {
21761        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21762    });
21763    cx.update_editor(|_, _, test_editor_cx| {
21764        assert_eq!(
21765            editors.len(),
21766            1,
21767            "Initially, only one, test, editor should be open in the workspace"
21768        );
21769        assert_eq!(
21770            test_editor_cx.entity(),
21771            editors.last().expect("Asserted len is 1").clone()
21772        );
21773    });
21774
21775    set_up_lsp_handlers(true, &mut cx);
21776    let navigated = cx
21777        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21778        .await
21779        .expect("Failed to navigate to lookup references");
21780    assert_eq!(
21781        navigated,
21782        Navigated::Yes,
21783        "Should have navigated to references as a fallback after empty GoToDefinition response"
21784    );
21785    // We should not change the selections in the existing file,
21786    // if opening another milti buffer with the references
21787    cx.assert_editor_state(
21788        &r#"fn one() {
21789            let mut a = two();
21790        }
21791
21792        fn «twoˇ»() {}"#
21793            .unindent(),
21794    );
21795    let editors = cx.update_workspace(|workspace, _, cx| {
21796        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21797    });
21798    cx.update_editor(|_, _, test_editor_cx| {
21799        assert_eq!(
21800            editors.len(),
21801            2,
21802            "After falling back to references search, we open a new editor with the results"
21803        );
21804        let references_fallback_text = editors
21805            .into_iter()
21806            .find(|new_editor| *new_editor != test_editor_cx.entity())
21807            .expect("Should have one non-test editor now")
21808            .read(test_editor_cx)
21809            .text(test_editor_cx);
21810        assert_eq!(
21811            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
21812            "Should use the range from the references response and not the GoToDefinition one"
21813        );
21814    });
21815}
21816
21817#[gpui::test]
21818async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
21819    init_test(cx, |_| {});
21820    cx.update(|cx| {
21821        let mut editor_settings = EditorSettings::get_global(cx).clone();
21822        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
21823        EditorSettings::override_global(editor_settings, cx);
21824    });
21825    let mut cx = EditorLspTestContext::new_rust(
21826        lsp::ServerCapabilities {
21827            definition_provider: Some(lsp::OneOf::Left(true)),
21828            references_provider: Some(lsp::OneOf::Left(true)),
21829            ..lsp::ServerCapabilities::default()
21830        },
21831        cx,
21832    )
21833    .await;
21834    let original_state = r#"fn one() {
21835        let mut a = ˇtwo();
21836    }
21837
21838    fn two() {}"#
21839        .unindent();
21840    cx.set_state(&original_state);
21841
21842    let mut go_to_definition = cx
21843        .lsp
21844        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21845            move |_, _| async move { Ok(None) },
21846        );
21847    let _references = cx
21848        .lsp
21849        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
21850            panic!("Should not call for references with no go to definition fallback")
21851        });
21852
21853    let navigated = cx
21854        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21855        .await
21856        .expect("Failed to navigate to lookup references");
21857    go_to_definition
21858        .next()
21859        .await
21860        .expect("Should have called the go_to_definition handler");
21861
21862    assert_eq!(
21863        navigated,
21864        Navigated::No,
21865        "Should have navigated to references as a fallback after empty GoToDefinition response"
21866    );
21867    cx.assert_editor_state(&original_state);
21868    let editors = cx.update_workspace(|workspace, _, cx| {
21869        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21870    });
21871    cx.update_editor(|_, _, _| {
21872        assert_eq!(
21873            editors.len(),
21874            1,
21875            "After unsuccessful fallback, no other editor should have been opened"
21876        );
21877    });
21878}
21879
21880#[gpui::test]
21881async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
21882    init_test(cx, |_| {});
21883    let mut cx = EditorLspTestContext::new_rust(
21884        lsp::ServerCapabilities {
21885            references_provider: Some(lsp::OneOf::Left(true)),
21886            ..lsp::ServerCapabilities::default()
21887        },
21888        cx,
21889    )
21890    .await;
21891
21892    cx.set_state(
21893        &r#"
21894        fn one() {
21895            let mut a = two();
21896        }
21897
21898        fn ˇtwo() {}"#
21899            .unindent(),
21900    );
21901    cx.lsp
21902        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21903            Ok(Some(vec![
21904                lsp::Location {
21905                    uri: params.text_document_position.text_document.uri.clone(),
21906                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21907                },
21908                lsp::Location {
21909                    uri: params.text_document_position.text_document.uri,
21910                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
21911                },
21912            ]))
21913        });
21914    let navigated = cx
21915        .update_editor(|editor, window, cx| {
21916            editor.find_all_references(&FindAllReferences, window, cx)
21917        })
21918        .unwrap()
21919        .await
21920        .expect("Failed to navigate to references");
21921    assert_eq!(
21922        navigated,
21923        Navigated::Yes,
21924        "Should have navigated to references from the FindAllReferences response"
21925    );
21926    cx.assert_editor_state(
21927        &r#"fn one() {
21928            let mut a = two();
21929        }
21930
21931        fn ˇtwo() {}"#
21932            .unindent(),
21933    );
21934
21935    let editors = cx.update_workspace(|workspace, _, cx| {
21936        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21937    });
21938    cx.update_editor(|_, _, _| {
21939        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
21940    });
21941
21942    cx.set_state(
21943        &r#"fn one() {
21944            let mut a = ˇtwo();
21945        }
21946
21947        fn two() {}"#
21948            .unindent(),
21949    );
21950    let navigated = cx
21951        .update_editor(|editor, window, cx| {
21952            editor.find_all_references(&FindAllReferences, window, cx)
21953        })
21954        .unwrap()
21955        .await
21956        .expect("Failed to navigate to references");
21957    assert_eq!(
21958        navigated,
21959        Navigated::Yes,
21960        "Should have navigated to references from the FindAllReferences response"
21961    );
21962    cx.assert_editor_state(
21963        &r#"fn one() {
21964            let mut a = ˇtwo();
21965        }
21966
21967        fn two() {}"#
21968            .unindent(),
21969    );
21970    let editors = cx.update_workspace(|workspace, _, cx| {
21971        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21972    });
21973    cx.update_editor(|_, _, _| {
21974        assert_eq!(
21975            editors.len(),
21976            2,
21977            "should have re-used the previous multibuffer"
21978        );
21979    });
21980
21981    cx.set_state(
21982        &r#"fn one() {
21983            let mut a = ˇtwo();
21984        }
21985        fn three() {}
21986        fn two() {}"#
21987            .unindent(),
21988    );
21989    cx.lsp
21990        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21991            Ok(Some(vec![
21992                lsp::Location {
21993                    uri: params.text_document_position.text_document.uri.clone(),
21994                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21995                },
21996                lsp::Location {
21997                    uri: params.text_document_position.text_document.uri,
21998                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
21999                },
22000            ]))
22001        });
22002    let navigated = cx
22003        .update_editor(|editor, window, cx| {
22004            editor.find_all_references(&FindAllReferences, window, cx)
22005        })
22006        .unwrap()
22007        .await
22008        .expect("Failed to navigate to references");
22009    assert_eq!(
22010        navigated,
22011        Navigated::Yes,
22012        "Should have navigated to references from the FindAllReferences response"
22013    );
22014    cx.assert_editor_state(
22015        &r#"fn one() {
22016                let mut a = ˇtwo();
22017            }
22018            fn three() {}
22019            fn two() {}"#
22020            .unindent(),
22021    );
22022    let editors = cx.update_workspace(|workspace, _, cx| {
22023        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22024    });
22025    cx.update_editor(|_, _, _| {
22026        assert_eq!(
22027            editors.len(),
22028            3,
22029            "should have used a new multibuffer as offsets changed"
22030        );
22031    });
22032}
22033#[gpui::test]
22034async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
22035    init_test(cx, |_| {});
22036
22037    let language = Arc::new(Language::new(
22038        LanguageConfig::default(),
22039        Some(tree_sitter_rust::LANGUAGE.into()),
22040    ));
22041
22042    let text = r#"
22043        #[cfg(test)]
22044        mod tests() {
22045            #[test]
22046            fn runnable_1() {
22047                let a = 1;
22048            }
22049
22050            #[test]
22051            fn runnable_2() {
22052                let a = 1;
22053                let b = 2;
22054            }
22055        }
22056    "#
22057    .unindent();
22058
22059    let fs = FakeFs::new(cx.executor());
22060    fs.insert_file("/file.rs", Default::default()).await;
22061
22062    let project = Project::test(fs, ["/a".as_ref()], cx).await;
22063    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22064    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22065    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
22066    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
22067
22068    let editor = cx.new_window_entity(|window, cx| {
22069        Editor::new(
22070            EditorMode::full(),
22071            multi_buffer,
22072            Some(project.clone()),
22073            window,
22074            cx,
22075        )
22076    });
22077
22078    editor.update_in(cx, |editor, window, cx| {
22079        let snapshot = editor.buffer().read(cx).snapshot(cx);
22080        editor.tasks.insert(
22081            (buffer.read(cx).remote_id(), 3),
22082            RunnableTasks {
22083                templates: vec![],
22084                offset: snapshot.anchor_before(43),
22085                column: 0,
22086                extra_variables: HashMap::default(),
22087                context_range: BufferOffset(43)..BufferOffset(85),
22088            },
22089        );
22090        editor.tasks.insert(
22091            (buffer.read(cx).remote_id(), 8),
22092            RunnableTasks {
22093                templates: vec![],
22094                offset: snapshot.anchor_before(86),
22095                column: 0,
22096                extra_variables: HashMap::default(),
22097                context_range: BufferOffset(86)..BufferOffset(191),
22098            },
22099        );
22100
22101        // Test finding task when cursor is inside function body
22102        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22103            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
22104        });
22105        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
22106        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
22107
22108        // Test finding task when cursor is on function name
22109        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22110            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
22111        });
22112        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
22113        assert_eq!(row, 8, "Should find task when cursor is on function name");
22114    });
22115}
22116
22117#[gpui::test]
22118async fn test_folding_buffers(cx: &mut TestAppContext) {
22119    init_test(cx, |_| {});
22120
22121    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
22122    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
22123    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
22124
22125    let fs = FakeFs::new(cx.executor());
22126    fs.insert_tree(
22127        path!("/a"),
22128        json!({
22129            "first.rs": sample_text_1,
22130            "second.rs": sample_text_2,
22131            "third.rs": sample_text_3,
22132        }),
22133    )
22134    .await;
22135    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22136    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22137    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22138    let worktree = project.update(cx, |project, cx| {
22139        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22140        assert_eq!(worktrees.len(), 1);
22141        worktrees.pop().unwrap()
22142    });
22143    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22144
22145    let buffer_1 = project
22146        .update(cx, |project, cx| {
22147            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
22148        })
22149        .await
22150        .unwrap();
22151    let buffer_2 = project
22152        .update(cx, |project, cx| {
22153            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
22154        })
22155        .await
22156        .unwrap();
22157    let buffer_3 = project
22158        .update(cx, |project, cx| {
22159            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
22160        })
22161        .await
22162        .unwrap();
22163
22164    let multi_buffer = cx.new(|cx| {
22165        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22166        multi_buffer.push_excerpts(
22167            buffer_1.clone(),
22168            [
22169                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22170                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22171                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22172            ],
22173            cx,
22174        );
22175        multi_buffer.push_excerpts(
22176            buffer_2.clone(),
22177            [
22178                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22179                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22180                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22181            ],
22182            cx,
22183        );
22184        multi_buffer.push_excerpts(
22185            buffer_3.clone(),
22186            [
22187                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22188                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22189                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22190            ],
22191            cx,
22192        );
22193        multi_buffer
22194    });
22195    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22196        Editor::new(
22197            EditorMode::full(),
22198            multi_buffer.clone(),
22199            Some(project.clone()),
22200            window,
22201            cx,
22202        )
22203    });
22204
22205    assert_eq!(
22206        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22207        "\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",
22208    );
22209
22210    multi_buffer_editor.update(cx, |editor, cx| {
22211        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
22212    });
22213    assert_eq!(
22214        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22215        "\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",
22216        "After folding the first buffer, its text should not be displayed"
22217    );
22218
22219    multi_buffer_editor.update(cx, |editor, cx| {
22220        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
22221    });
22222    assert_eq!(
22223        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22224        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
22225        "After folding the second buffer, its text should not be displayed"
22226    );
22227
22228    multi_buffer_editor.update(cx, |editor, cx| {
22229        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
22230    });
22231    assert_eq!(
22232        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22233        "\n\n\n\n\n",
22234        "After folding the third buffer, its text should not be displayed"
22235    );
22236
22237    // Emulate selection inside the fold logic, that should work
22238    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22239        editor
22240            .snapshot(window, cx)
22241            .next_line_boundary(Point::new(0, 4));
22242    });
22243
22244    multi_buffer_editor.update(cx, |editor, cx| {
22245        editor.unfold_buffer(buffer_2.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\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
22250        "After unfolding the second buffer, its text should be displayed"
22251    );
22252
22253    // Typing inside of buffer 1 causes that buffer to be unfolded.
22254    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22255        assert_eq!(
22256            multi_buffer
22257                .read(cx)
22258                .snapshot(cx)
22259                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
22260                .collect::<String>(),
22261            "bbbb"
22262        );
22263        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22264            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
22265        });
22266        editor.handle_input("B", window, cx);
22267    });
22268
22269    assert_eq!(
22270        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22271        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
22272        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
22273    );
22274
22275    multi_buffer_editor.update(cx, |editor, cx| {
22276        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
22277    });
22278    assert_eq!(
22279        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22280        "\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",
22281        "After unfolding the all buffers, all original text should be displayed"
22282    );
22283}
22284
22285#[gpui::test]
22286async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
22287    init_test(cx, |_| {});
22288
22289    let sample_text_1 = "1111\n2222\n3333".to_string();
22290    let sample_text_2 = "4444\n5555\n6666".to_string();
22291    let sample_text_3 = "7777\n8888\n9999".to_string();
22292
22293    let fs = FakeFs::new(cx.executor());
22294    fs.insert_tree(
22295        path!("/a"),
22296        json!({
22297            "first.rs": sample_text_1,
22298            "second.rs": sample_text_2,
22299            "third.rs": sample_text_3,
22300        }),
22301    )
22302    .await;
22303    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22304    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22305    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22306    let worktree = project.update(cx, |project, cx| {
22307        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22308        assert_eq!(worktrees.len(), 1);
22309        worktrees.pop().unwrap()
22310    });
22311    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22312
22313    let buffer_1 = project
22314        .update(cx, |project, cx| {
22315            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
22316        })
22317        .await
22318        .unwrap();
22319    let buffer_2 = project
22320        .update(cx, |project, cx| {
22321            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
22322        })
22323        .await
22324        .unwrap();
22325    let buffer_3 = project
22326        .update(cx, |project, cx| {
22327            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
22328        })
22329        .await
22330        .unwrap();
22331
22332    let multi_buffer = cx.new(|cx| {
22333        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22334        multi_buffer.push_excerpts(
22335            buffer_1.clone(),
22336            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22337            cx,
22338        );
22339        multi_buffer.push_excerpts(
22340            buffer_2.clone(),
22341            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22342            cx,
22343        );
22344        multi_buffer.push_excerpts(
22345            buffer_3.clone(),
22346            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22347            cx,
22348        );
22349        multi_buffer
22350    });
22351
22352    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22353        Editor::new(
22354            EditorMode::full(),
22355            multi_buffer,
22356            Some(project.clone()),
22357            window,
22358            cx,
22359        )
22360    });
22361
22362    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
22363    assert_eq!(
22364        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22365        full_text,
22366    );
22367
22368    multi_buffer_editor.update(cx, |editor, cx| {
22369        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
22370    });
22371    assert_eq!(
22372        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22373        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
22374        "After folding the first buffer, its text should not be displayed"
22375    );
22376
22377    multi_buffer_editor.update(cx, |editor, cx| {
22378        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
22379    });
22380
22381    assert_eq!(
22382        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22383        "\n\n\n\n\n\n7777\n8888\n9999",
22384        "After folding the second buffer, its text should not be displayed"
22385    );
22386
22387    multi_buffer_editor.update(cx, |editor, cx| {
22388        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
22389    });
22390    assert_eq!(
22391        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22392        "\n\n\n\n\n",
22393        "After folding the third buffer, its text should not be displayed"
22394    );
22395
22396    multi_buffer_editor.update(cx, |editor, cx| {
22397        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
22398    });
22399    assert_eq!(
22400        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22401        "\n\n\n\n4444\n5555\n6666\n\n",
22402        "After unfolding the second buffer, its text should be displayed"
22403    );
22404
22405    multi_buffer_editor.update(cx, |editor, cx| {
22406        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
22407    });
22408    assert_eq!(
22409        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22410        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
22411        "After unfolding the first buffer, its text should be displayed"
22412    );
22413
22414    multi_buffer_editor.update(cx, |editor, cx| {
22415        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
22416    });
22417    assert_eq!(
22418        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22419        full_text,
22420        "After unfolding all buffers, all original text should be displayed"
22421    );
22422}
22423
22424#[gpui::test]
22425async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
22426    init_test(cx, |_| {});
22427
22428    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
22429
22430    let fs = FakeFs::new(cx.executor());
22431    fs.insert_tree(
22432        path!("/a"),
22433        json!({
22434            "main.rs": sample_text,
22435        }),
22436    )
22437    .await;
22438    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22439    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22440    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22441    let worktree = project.update(cx, |project, cx| {
22442        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22443        assert_eq!(worktrees.len(), 1);
22444        worktrees.pop().unwrap()
22445    });
22446    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22447
22448    let buffer_1 = project
22449        .update(cx, |project, cx| {
22450            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22451        })
22452        .await
22453        .unwrap();
22454
22455    let multi_buffer = cx.new(|cx| {
22456        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22457        multi_buffer.push_excerpts(
22458            buffer_1.clone(),
22459            [ExcerptRange::new(
22460                Point::new(0, 0)
22461                    ..Point::new(
22462                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
22463                        0,
22464                    ),
22465            )],
22466            cx,
22467        );
22468        multi_buffer
22469    });
22470    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22471        Editor::new(
22472            EditorMode::full(),
22473            multi_buffer,
22474            Some(project.clone()),
22475            window,
22476            cx,
22477        )
22478    });
22479
22480    let selection_range = Point::new(1, 0)..Point::new(2, 0);
22481    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22482        enum TestHighlight {}
22483        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
22484        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
22485        editor.highlight_text::<TestHighlight>(
22486            vec![highlight_range.clone()],
22487            HighlightStyle::color(Hsla::green()),
22488            cx,
22489        );
22490        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22491            s.select_ranges(Some(highlight_range))
22492        });
22493    });
22494
22495    let full_text = format!("\n\n{sample_text}");
22496    assert_eq!(
22497        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22498        full_text,
22499    );
22500}
22501
22502#[gpui::test]
22503async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
22504    init_test(cx, |_| {});
22505    cx.update(|cx| {
22506        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
22507            "keymaps/default-linux.json",
22508            cx,
22509        )
22510        .unwrap();
22511        cx.bind_keys(default_key_bindings);
22512    });
22513
22514    let (editor, cx) = cx.add_window_view(|window, cx| {
22515        let multi_buffer = MultiBuffer::build_multi(
22516            [
22517                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
22518                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
22519                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
22520                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
22521            ],
22522            cx,
22523        );
22524        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
22525
22526        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
22527        // fold all but the second buffer, so that we test navigating between two
22528        // adjacent folded buffers, as well as folded buffers at the start and
22529        // end the multibuffer
22530        editor.fold_buffer(buffer_ids[0], cx);
22531        editor.fold_buffer(buffer_ids[2], cx);
22532        editor.fold_buffer(buffer_ids[3], cx);
22533
22534        editor
22535    });
22536    cx.simulate_resize(size(px(1000.), px(1000.)));
22537
22538    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
22539    cx.assert_excerpts_with_selections(indoc! {"
22540        [EXCERPT]
22541        ˇ[FOLDED]
22542        [EXCERPT]
22543        a1
22544        b1
22545        [EXCERPT]
22546        [FOLDED]
22547        [EXCERPT]
22548        [FOLDED]
22549        "
22550    });
22551    cx.simulate_keystroke("down");
22552    cx.assert_excerpts_with_selections(indoc! {"
22553        [EXCERPT]
22554        [FOLDED]
22555        [EXCERPT]
22556        ˇa1
22557        b1
22558        [EXCERPT]
22559        [FOLDED]
22560        [EXCERPT]
22561        [FOLDED]
22562        "
22563    });
22564    cx.simulate_keystroke("down");
22565    cx.assert_excerpts_with_selections(indoc! {"
22566        [EXCERPT]
22567        [FOLDED]
22568        [EXCERPT]
22569        a1
22570        ˇb1
22571        [EXCERPT]
22572        [FOLDED]
22573        [EXCERPT]
22574        [FOLDED]
22575        "
22576    });
22577    cx.simulate_keystroke("down");
22578    cx.assert_excerpts_with_selections(indoc! {"
22579        [EXCERPT]
22580        [FOLDED]
22581        [EXCERPT]
22582        a1
22583        b1
22584        ˇ[EXCERPT]
22585        [FOLDED]
22586        [EXCERPT]
22587        [FOLDED]
22588        "
22589    });
22590    cx.simulate_keystroke("down");
22591    cx.assert_excerpts_with_selections(indoc! {"
22592        [EXCERPT]
22593        [FOLDED]
22594        [EXCERPT]
22595        a1
22596        b1
22597        [EXCERPT]
22598        ˇ[FOLDED]
22599        [EXCERPT]
22600        [FOLDED]
22601        "
22602    });
22603    for _ in 0..5 {
22604        cx.simulate_keystroke("down");
22605        cx.assert_excerpts_with_selections(indoc! {"
22606            [EXCERPT]
22607            [FOLDED]
22608            [EXCERPT]
22609            a1
22610            b1
22611            [EXCERPT]
22612            [FOLDED]
22613            [EXCERPT]
22614            ˇ[FOLDED]
22615            "
22616        });
22617    }
22618
22619    cx.simulate_keystroke("up");
22620    cx.assert_excerpts_with_selections(indoc! {"
22621        [EXCERPT]
22622        [FOLDED]
22623        [EXCERPT]
22624        a1
22625        b1
22626        [EXCERPT]
22627        ˇ[FOLDED]
22628        [EXCERPT]
22629        [FOLDED]
22630        "
22631    });
22632    cx.simulate_keystroke("up");
22633    cx.assert_excerpts_with_selections(indoc! {"
22634        [EXCERPT]
22635        [FOLDED]
22636        [EXCERPT]
22637        a1
22638        b1
22639        ˇ[EXCERPT]
22640        [FOLDED]
22641        [EXCERPT]
22642        [FOLDED]
22643        "
22644    });
22645    cx.simulate_keystroke("up");
22646    cx.assert_excerpts_with_selections(indoc! {"
22647        [EXCERPT]
22648        [FOLDED]
22649        [EXCERPT]
22650        a1
22651        ˇb1
22652        [EXCERPT]
22653        [FOLDED]
22654        [EXCERPT]
22655        [FOLDED]
22656        "
22657    });
22658    cx.simulate_keystroke("up");
22659    cx.assert_excerpts_with_selections(indoc! {"
22660        [EXCERPT]
22661        [FOLDED]
22662        [EXCERPT]
22663        ˇa1
22664        b1
22665        [EXCERPT]
22666        [FOLDED]
22667        [EXCERPT]
22668        [FOLDED]
22669        "
22670    });
22671    for _ in 0..5 {
22672        cx.simulate_keystroke("up");
22673        cx.assert_excerpts_with_selections(indoc! {"
22674            [EXCERPT]
22675            ˇ[FOLDED]
22676            [EXCERPT]
22677            a1
22678            b1
22679            [EXCERPT]
22680            [FOLDED]
22681            [EXCERPT]
22682            [FOLDED]
22683            "
22684        });
22685    }
22686}
22687
22688#[gpui::test]
22689async fn test_edit_prediction_text(cx: &mut TestAppContext) {
22690    init_test(cx, |_| {});
22691
22692    // Simple insertion
22693    assert_highlighted_edits(
22694        "Hello, world!",
22695        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
22696        true,
22697        cx,
22698        |highlighted_edits, cx| {
22699            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
22700            assert_eq!(highlighted_edits.highlights.len(), 1);
22701            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
22702            assert_eq!(
22703                highlighted_edits.highlights[0].1.background_color,
22704                Some(cx.theme().status().created_background)
22705            );
22706        },
22707    )
22708    .await;
22709
22710    // Replacement
22711    assert_highlighted_edits(
22712        "This is a test.",
22713        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
22714        false,
22715        cx,
22716        |highlighted_edits, cx| {
22717            assert_eq!(highlighted_edits.text, "That is a test.");
22718            assert_eq!(highlighted_edits.highlights.len(), 1);
22719            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
22720            assert_eq!(
22721                highlighted_edits.highlights[0].1.background_color,
22722                Some(cx.theme().status().created_background)
22723            );
22724        },
22725    )
22726    .await;
22727
22728    // Multiple edits
22729    assert_highlighted_edits(
22730        "Hello, world!",
22731        vec![
22732            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
22733            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
22734        ],
22735        false,
22736        cx,
22737        |highlighted_edits, cx| {
22738            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
22739            assert_eq!(highlighted_edits.highlights.len(), 2);
22740            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
22741            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
22742            assert_eq!(
22743                highlighted_edits.highlights[0].1.background_color,
22744                Some(cx.theme().status().created_background)
22745            );
22746            assert_eq!(
22747                highlighted_edits.highlights[1].1.background_color,
22748                Some(cx.theme().status().created_background)
22749            );
22750        },
22751    )
22752    .await;
22753
22754    // Multiple lines with edits
22755    assert_highlighted_edits(
22756        "First line\nSecond line\nThird line\nFourth line",
22757        vec![
22758            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
22759            (
22760                Point::new(2, 0)..Point::new(2, 10),
22761                "New third line".to_string(),
22762            ),
22763            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
22764        ],
22765        false,
22766        cx,
22767        |highlighted_edits, cx| {
22768            assert_eq!(
22769                highlighted_edits.text,
22770                "Second modified\nNew third line\nFourth updated line"
22771            );
22772            assert_eq!(highlighted_edits.highlights.len(), 3);
22773            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
22774            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
22775            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
22776            for highlight in &highlighted_edits.highlights {
22777                assert_eq!(
22778                    highlight.1.background_color,
22779                    Some(cx.theme().status().created_background)
22780                );
22781            }
22782        },
22783    )
22784    .await;
22785}
22786
22787#[gpui::test]
22788async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
22789    init_test(cx, |_| {});
22790
22791    // Deletion
22792    assert_highlighted_edits(
22793        "Hello, world!",
22794        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
22795        true,
22796        cx,
22797        |highlighted_edits, cx| {
22798            assert_eq!(highlighted_edits.text, "Hello, world!");
22799            assert_eq!(highlighted_edits.highlights.len(), 1);
22800            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
22801            assert_eq!(
22802                highlighted_edits.highlights[0].1.background_color,
22803                Some(cx.theme().status().deleted_background)
22804            );
22805        },
22806    )
22807    .await;
22808
22809    // Insertion
22810    assert_highlighted_edits(
22811        "Hello, world!",
22812        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
22813        true,
22814        cx,
22815        |highlighted_edits, cx| {
22816            assert_eq!(highlighted_edits.highlights.len(), 1);
22817            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
22818            assert_eq!(
22819                highlighted_edits.highlights[0].1.background_color,
22820                Some(cx.theme().status().created_background)
22821            );
22822        },
22823    )
22824    .await;
22825}
22826
22827async fn assert_highlighted_edits(
22828    text: &str,
22829    edits: Vec<(Range<Point>, String)>,
22830    include_deletions: bool,
22831    cx: &mut TestAppContext,
22832    assertion_fn: impl Fn(HighlightedText, &App),
22833) {
22834    let window = cx.add_window(|window, cx| {
22835        let buffer = MultiBuffer::build_simple(text, cx);
22836        Editor::new(EditorMode::full(), buffer, None, window, cx)
22837    });
22838    let cx = &mut VisualTestContext::from_window(*window, cx);
22839
22840    let (buffer, snapshot) = window
22841        .update(cx, |editor, _window, cx| {
22842            (
22843                editor.buffer().clone(),
22844                editor.buffer().read(cx).snapshot(cx),
22845            )
22846        })
22847        .unwrap();
22848
22849    let edits = edits
22850        .into_iter()
22851        .map(|(range, edit)| {
22852            (
22853                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
22854                edit,
22855            )
22856        })
22857        .collect::<Vec<_>>();
22858
22859    let text_anchor_edits = edits
22860        .clone()
22861        .into_iter()
22862        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit.into()))
22863        .collect::<Vec<_>>();
22864
22865    let edit_preview = window
22866        .update(cx, |_, _window, cx| {
22867            buffer
22868                .read(cx)
22869                .as_singleton()
22870                .unwrap()
22871                .read(cx)
22872                .preview_edits(text_anchor_edits.into(), cx)
22873        })
22874        .unwrap()
22875        .await;
22876
22877    cx.update(|_window, cx| {
22878        let highlighted_edits = edit_prediction_edit_text(
22879            snapshot.as_singleton().unwrap().2,
22880            &edits,
22881            &edit_preview,
22882            include_deletions,
22883            cx,
22884        );
22885        assertion_fn(highlighted_edits, cx)
22886    });
22887}
22888
22889#[track_caller]
22890fn assert_breakpoint(
22891    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
22892    path: &Arc<Path>,
22893    expected: Vec<(u32, Breakpoint)>,
22894) {
22895    if expected.is_empty() {
22896        assert!(!breakpoints.contains_key(path), "{}", path.display());
22897    } else {
22898        let mut breakpoint = breakpoints
22899            .get(path)
22900            .unwrap()
22901            .iter()
22902            .map(|breakpoint| {
22903                (
22904                    breakpoint.row,
22905                    Breakpoint {
22906                        message: breakpoint.message.clone(),
22907                        state: breakpoint.state,
22908                        condition: breakpoint.condition.clone(),
22909                        hit_condition: breakpoint.hit_condition.clone(),
22910                    },
22911                )
22912            })
22913            .collect::<Vec<_>>();
22914
22915        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
22916
22917        assert_eq!(expected, breakpoint);
22918    }
22919}
22920
22921fn add_log_breakpoint_at_cursor(
22922    editor: &mut Editor,
22923    log_message: &str,
22924    window: &mut Window,
22925    cx: &mut Context<Editor>,
22926) {
22927    let (anchor, bp) = editor
22928        .breakpoints_at_cursors(window, cx)
22929        .first()
22930        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
22931        .unwrap_or_else(|| {
22932            let snapshot = editor.snapshot(window, cx);
22933            let cursor_position: Point =
22934                editor.selections.newest(&snapshot.display_snapshot).head();
22935
22936            let breakpoint_position = snapshot
22937                .buffer_snapshot()
22938                .anchor_before(Point::new(cursor_position.row, 0));
22939
22940            (breakpoint_position, Breakpoint::new_log(log_message))
22941        });
22942
22943    editor.edit_breakpoint_at_anchor(
22944        anchor,
22945        bp,
22946        BreakpointEditAction::EditLogMessage(log_message.into()),
22947        cx,
22948    );
22949}
22950
22951#[gpui::test]
22952async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
22953    init_test(cx, |_| {});
22954
22955    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22956    let fs = FakeFs::new(cx.executor());
22957    fs.insert_tree(
22958        path!("/a"),
22959        json!({
22960            "main.rs": sample_text,
22961        }),
22962    )
22963    .await;
22964    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22965    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22966    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22967
22968    let fs = FakeFs::new(cx.executor());
22969    fs.insert_tree(
22970        path!("/a"),
22971        json!({
22972            "main.rs": sample_text,
22973        }),
22974    )
22975    .await;
22976    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22977    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22978    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22979    let worktree_id = workspace
22980        .update(cx, |workspace, _window, cx| {
22981            workspace.project().update(cx, |project, cx| {
22982                project.worktrees(cx).next().unwrap().read(cx).id()
22983            })
22984        })
22985        .unwrap();
22986
22987    let buffer = project
22988        .update(cx, |project, cx| {
22989            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22990        })
22991        .await
22992        .unwrap();
22993
22994    let (editor, cx) = cx.add_window_view(|window, cx| {
22995        Editor::new(
22996            EditorMode::full(),
22997            MultiBuffer::build_from_buffer(buffer, cx),
22998            Some(project.clone()),
22999            window,
23000            cx,
23001        )
23002    });
23003
23004    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23005    let abs_path = project.read_with(cx, |project, cx| {
23006        project
23007            .absolute_path(&project_path, cx)
23008            .map(Arc::from)
23009            .unwrap()
23010    });
23011
23012    // assert we can add breakpoint on the first line
23013    editor.update_in(cx, |editor, window, cx| {
23014        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23015        editor.move_to_end(&MoveToEnd, window, cx);
23016        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23017    });
23018
23019    let breakpoints = editor.update(cx, |editor, cx| {
23020        editor
23021            .breakpoint_store()
23022            .as_ref()
23023            .unwrap()
23024            .read(cx)
23025            .all_source_breakpoints(cx)
23026    });
23027
23028    assert_eq!(1, breakpoints.len());
23029    assert_breakpoint(
23030        &breakpoints,
23031        &abs_path,
23032        vec![
23033            (0, Breakpoint::new_standard()),
23034            (3, Breakpoint::new_standard()),
23035        ],
23036    );
23037
23038    editor.update_in(cx, |editor, window, cx| {
23039        editor.move_to_beginning(&MoveToBeginning, window, cx);
23040        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23041    });
23042
23043    let breakpoints = editor.update(cx, |editor, cx| {
23044        editor
23045            .breakpoint_store()
23046            .as_ref()
23047            .unwrap()
23048            .read(cx)
23049            .all_source_breakpoints(cx)
23050    });
23051
23052    assert_eq!(1, breakpoints.len());
23053    assert_breakpoint(
23054        &breakpoints,
23055        &abs_path,
23056        vec![(3, Breakpoint::new_standard())],
23057    );
23058
23059    editor.update_in(cx, |editor, window, cx| {
23060        editor.move_to_end(&MoveToEnd, window, cx);
23061        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23062    });
23063
23064    let breakpoints = editor.update(cx, |editor, cx| {
23065        editor
23066            .breakpoint_store()
23067            .as_ref()
23068            .unwrap()
23069            .read(cx)
23070            .all_source_breakpoints(cx)
23071    });
23072
23073    assert_eq!(0, breakpoints.len());
23074    assert_breakpoint(&breakpoints, &abs_path, vec![]);
23075}
23076
23077#[gpui::test]
23078async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
23079    init_test(cx, |_| {});
23080
23081    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23082
23083    let fs = FakeFs::new(cx.executor());
23084    fs.insert_tree(
23085        path!("/a"),
23086        json!({
23087            "main.rs": sample_text,
23088        }),
23089    )
23090    .await;
23091    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23092    let (workspace, cx) =
23093        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23094
23095    let worktree_id = workspace.update(cx, |workspace, cx| {
23096        workspace.project().update(cx, |project, cx| {
23097            project.worktrees(cx).next().unwrap().read(cx).id()
23098        })
23099    });
23100
23101    let buffer = project
23102        .update(cx, |project, cx| {
23103            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23104        })
23105        .await
23106        .unwrap();
23107
23108    let (editor, cx) = cx.add_window_view(|window, cx| {
23109        Editor::new(
23110            EditorMode::full(),
23111            MultiBuffer::build_from_buffer(buffer, cx),
23112            Some(project.clone()),
23113            window,
23114            cx,
23115        )
23116    });
23117
23118    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23119    let abs_path = project.read_with(cx, |project, cx| {
23120        project
23121            .absolute_path(&project_path, cx)
23122            .map(Arc::from)
23123            .unwrap()
23124    });
23125
23126    editor.update_in(cx, |editor, window, cx| {
23127        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
23128    });
23129
23130    let breakpoints = editor.update(cx, |editor, cx| {
23131        editor
23132            .breakpoint_store()
23133            .as_ref()
23134            .unwrap()
23135            .read(cx)
23136            .all_source_breakpoints(cx)
23137    });
23138
23139    assert_breakpoint(
23140        &breakpoints,
23141        &abs_path,
23142        vec![(0, Breakpoint::new_log("hello world"))],
23143    );
23144
23145    // Removing a log message from a log breakpoint should remove it
23146    editor.update_in(cx, |editor, window, cx| {
23147        add_log_breakpoint_at_cursor(editor, "", window, cx);
23148    });
23149
23150    let breakpoints = editor.update(cx, |editor, cx| {
23151        editor
23152            .breakpoint_store()
23153            .as_ref()
23154            .unwrap()
23155            .read(cx)
23156            .all_source_breakpoints(cx)
23157    });
23158
23159    assert_breakpoint(&breakpoints, &abs_path, vec![]);
23160
23161    editor.update_in(cx, |editor, window, cx| {
23162        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23163        editor.move_to_end(&MoveToEnd, window, cx);
23164        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23165        // Not adding a log message to a standard breakpoint shouldn't remove it
23166        add_log_breakpoint_at_cursor(editor, "", window, cx);
23167    });
23168
23169    let breakpoints = editor.update(cx, |editor, cx| {
23170        editor
23171            .breakpoint_store()
23172            .as_ref()
23173            .unwrap()
23174            .read(cx)
23175            .all_source_breakpoints(cx)
23176    });
23177
23178    assert_breakpoint(
23179        &breakpoints,
23180        &abs_path,
23181        vec![
23182            (0, Breakpoint::new_standard()),
23183            (3, Breakpoint::new_standard()),
23184        ],
23185    );
23186
23187    editor.update_in(cx, |editor, window, cx| {
23188        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
23189    });
23190
23191    let breakpoints = editor.update(cx, |editor, cx| {
23192        editor
23193            .breakpoint_store()
23194            .as_ref()
23195            .unwrap()
23196            .read(cx)
23197            .all_source_breakpoints(cx)
23198    });
23199
23200    assert_breakpoint(
23201        &breakpoints,
23202        &abs_path,
23203        vec![
23204            (0, Breakpoint::new_standard()),
23205            (3, Breakpoint::new_log("hello world")),
23206        ],
23207    );
23208
23209    editor.update_in(cx, |editor, window, cx| {
23210        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
23211    });
23212
23213    let breakpoints = editor.update(cx, |editor, cx| {
23214        editor
23215            .breakpoint_store()
23216            .as_ref()
23217            .unwrap()
23218            .read(cx)
23219            .all_source_breakpoints(cx)
23220    });
23221
23222    assert_breakpoint(
23223        &breakpoints,
23224        &abs_path,
23225        vec![
23226            (0, Breakpoint::new_standard()),
23227            (3, Breakpoint::new_log("hello Earth!!")),
23228        ],
23229    );
23230}
23231
23232/// This also tests that Editor::breakpoint_at_cursor_head is working properly
23233/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
23234/// or when breakpoints were placed out of order. This tests for a regression too
23235#[gpui::test]
23236async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
23237    init_test(cx, |_| {});
23238
23239    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23240    let fs = FakeFs::new(cx.executor());
23241    fs.insert_tree(
23242        path!("/a"),
23243        json!({
23244            "main.rs": sample_text,
23245        }),
23246    )
23247    .await;
23248    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23249    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23250    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23251
23252    let fs = FakeFs::new(cx.executor());
23253    fs.insert_tree(
23254        path!("/a"),
23255        json!({
23256            "main.rs": sample_text,
23257        }),
23258    )
23259    .await;
23260    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23261    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23262    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23263    let worktree_id = workspace
23264        .update(cx, |workspace, _window, cx| {
23265            workspace.project().update(cx, |project, cx| {
23266                project.worktrees(cx).next().unwrap().read(cx).id()
23267            })
23268        })
23269        .unwrap();
23270
23271    let buffer = project
23272        .update(cx, |project, cx| {
23273            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23274        })
23275        .await
23276        .unwrap();
23277
23278    let (editor, cx) = cx.add_window_view(|window, cx| {
23279        Editor::new(
23280            EditorMode::full(),
23281            MultiBuffer::build_from_buffer(buffer, cx),
23282            Some(project.clone()),
23283            window,
23284            cx,
23285        )
23286    });
23287
23288    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23289    let abs_path = project.read_with(cx, |project, cx| {
23290        project
23291            .absolute_path(&project_path, cx)
23292            .map(Arc::from)
23293            .unwrap()
23294    });
23295
23296    // assert we can add breakpoint on the first line
23297    editor.update_in(cx, |editor, window, cx| {
23298        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23299        editor.move_to_end(&MoveToEnd, window, cx);
23300        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23301        editor.move_up(&MoveUp, window, cx);
23302        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23303    });
23304
23305    let breakpoints = editor.update(cx, |editor, cx| {
23306        editor
23307            .breakpoint_store()
23308            .as_ref()
23309            .unwrap()
23310            .read(cx)
23311            .all_source_breakpoints(cx)
23312    });
23313
23314    assert_eq!(1, breakpoints.len());
23315    assert_breakpoint(
23316        &breakpoints,
23317        &abs_path,
23318        vec![
23319            (0, Breakpoint::new_standard()),
23320            (2, Breakpoint::new_standard()),
23321            (3, Breakpoint::new_standard()),
23322        ],
23323    );
23324
23325    editor.update_in(cx, |editor, window, cx| {
23326        editor.move_to_beginning(&MoveToBeginning, window, cx);
23327        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23328        editor.move_to_end(&MoveToEnd, window, cx);
23329        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23330        // Disabling a breakpoint that doesn't exist should do nothing
23331        editor.move_up(&MoveUp, window, cx);
23332        editor.move_up(&MoveUp, window, cx);
23333        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23334    });
23335
23336    let breakpoints = editor.update(cx, |editor, cx| {
23337        editor
23338            .breakpoint_store()
23339            .as_ref()
23340            .unwrap()
23341            .read(cx)
23342            .all_source_breakpoints(cx)
23343    });
23344
23345    let disable_breakpoint = {
23346        let mut bp = Breakpoint::new_standard();
23347        bp.state = BreakpointState::Disabled;
23348        bp
23349    };
23350
23351    assert_eq!(1, breakpoints.len());
23352    assert_breakpoint(
23353        &breakpoints,
23354        &abs_path,
23355        vec![
23356            (0, disable_breakpoint.clone()),
23357            (2, Breakpoint::new_standard()),
23358            (3, disable_breakpoint.clone()),
23359        ],
23360    );
23361
23362    editor.update_in(cx, |editor, window, cx| {
23363        editor.move_to_beginning(&MoveToBeginning, window, cx);
23364        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23365        editor.move_to_end(&MoveToEnd, window, cx);
23366        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23367        editor.move_up(&MoveUp, window, cx);
23368        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23369    });
23370
23371    let breakpoints = editor.update(cx, |editor, cx| {
23372        editor
23373            .breakpoint_store()
23374            .as_ref()
23375            .unwrap()
23376            .read(cx)
23377            .all_source_breakpoints(cx)
23378    });
23379
23380    assert_eq!(1, breakpoints.len());
23381    assert_breakpoint(
23382        &breakpoints,
23383        &abs_path,
23384        vec![
23385            (0, Breakpoint::new_standard()),
23386            (2, disable_breakpoint),
23387            (3, Breakpoint::new_standard()),
23388        ],
23389    );
23390}
23391
23392#[gpui::test]
23393async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
23394    init_test(cx, |_| {});
23395    let capabilities = lsp::ServerCapabilities {
23396        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
23397            prepare_provider: Some(true),
23398            work_done_progress_options: Default::default(),
23399        })),
23400        ..Default::default()
23401    };
23402    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23403
23404    cx.set_state(indoc! {"
23405        struct Fˇoo {}
23406    "});
23407
23408    cx.update_editor(|editor, _, cx| {
23409        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23410        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23411        editor.highlight_background::<DocumentHighlightRead>(
23412            &[highlight_range],
23413            |theme| theme.colors().editor_document_highlight_read_background,
23414            cx,
23415        );
23416    });
23417
23418    let mut prepare_rename_handler = cx
23419        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
23420            move |_, _, _| async move {
23421                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
23422                    start: lsp::Position {
23423                        line: 0,
23424                        character: 7,
23425                    },
23426                    end: lsp::Position {
23427                        line: 0,
23428                        character: 10,
23429                    },
23430                })))
23431            },
23432        );
23433    let prepare_rename_task = cx
23434        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23435        .expect("Prepare rename was not started");
23436    prepare_rename_handler.next().await.unwrap();
23437    prepare_rename_task.await.expect("Prepare rename failed");
23438
23439    let mut rename_handler =
23440        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23441            let edit = lsp::TextEdit {
23442                range: lsp::Range {
23443                    start: lsp::Position {
23444                        line: 0,
23445                        character: 7,
23446                    },
23447                    end: lsp::Position {
23448                        line: 0,
23449                        character: 10,
23450                    },
23451                },
23452                new_text: "FooRenamed".to_string(),
23453            };
23454            Ok(Some(lsp::WorkspaceEdit::new(
23455                // Specify the same edit twice
23456                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
23457            )))
23458        });
23459    let rename_task = cx
23460        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23461        .expect("Confirm rename was not started");
23462    rename_handler.next().await.unwrap();
23463    rename_task.await.expect("Confirm rename failed");
23464    cx.run_until_parked();
23465
23466    // Despite two edits, only one is actually applied as those are identical
23467    cx.assert_editor_state(indoc! {"
23468        struct FooRenamedˇ {}
23469    "});
23470}
23471
23472#[gpui::test]
23473async fn test_rename_without_prepare(cx: &mut TestAppContext) {
23474    init_test(cx, |_| {});
23475    // These capabilities indicate that the server does not support prepare rename.
23476    let capabilities = lsp::ServerCapabilities {
23477        rename_provider: Some(lsp::OneOf::Left(true)),
23478        ..Default::default()
23479    };
23480    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23481
23482    cx.set_state(indoc! {"
23483        struct Fˇoo {}
23484    "});
23485
23486    cx.update_editor(|editor, _window, cx| {
23487        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23488        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23489        editor.highlight_background::<DocumentHighlightRead>(
23490            &[highlight_range],
23491            |theme| theme.colors().editor_document_highlight_read_background,
23492            cx,
23493        );
23494    });
23495
23496    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23497        .expect("Prepare rename was not started")
23498        .await
23499        .expect("Prepare rename failed");
23500
23501    let mut rename_handler =
23502        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23503            let edit = lsp::TextEdit {
23504                range: lsp::Range {
23505                    start: lsp::Position {
23506                        line: 0,
23507                        character: 7,
23508                    },
23509                    end: lsp::Position {
23510                        line: 0,
23511                        character: 10,
23512                    },
23513                },
23514                new_text: "FooRenamed".to_string(),
23515            };
23516            Ok(Some(lsp::WorkspaceEdit::new(
23517                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
23518            )))
23519        });
23520    let rename_task = cx
23521        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23522        .expect("Confirm rename was not started");
23523    rename_handler.next().await.unwrap();
23524    rename_task.await.expect("Confirm rename failed");
23525    cx.run_until_parked();
23526
23527    // Correct range is renamed, as `surrounding_word` is used to find it.
23528    cx.assert_editor_state(indoc! {"
23529        struct FooRenamedˇ {}
23530    "});
23531}
23532
23533#[gpui::test]
23534async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
23535    init_test(cx, |_| {});
23536    let mut cx = EditorTestContext::new(cx).await;
23537
23538    let language = Arc::new(
23539        Language::new(
23540            LanguageConfig::default(),
23541            Some(tree_sitter_html::LANGUAGE.into()),
23542        )
23543        .with_brackets_query(
23544            r#"
23545            ("<" @open "/>" @close)
23546            ("</" @open ">" @close)
23547            ("<" @open ">" @close)
23548            ("\"" @open "\"" @close)
23549            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
23550        "#,
23551        )
23552        .unwrap(),
23553    );
23554    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23555
23556    cx.set_state(indoc! {"
23557        <span>ˇ</span>
23558    "});
23559    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23560    cx.assert_editor_state(indoc! {"
23561        <span>
23562        ˇ
23563        </span>
23564    "});
23565
23566    cx.set_state(indoc! {"
23567        <span><span></span>ˇ</span>
23568    "});
23569    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23570    cx.assert_editor_state(indoc! {"
23571        <span><span></span>
23572        ˇ</span>
23573    "});
23574
23575    cx.set_state(indoc! {"
23576        <span>ˇ
23577        </span>
23578    "});
23579    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23580    cx.assert_editor_state(indoc! {"
23581        <span>
23582        ˇ
23583        </span>
23584    "});
23585}
23586
23587#[gpui::test(iterations = 10)]
23588async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
23589    init_test(cx, |_| {});
23590
23591    let fs = FakeFs::new(cx.executor());
23592    fs.insert_tree(
23593        path!("/dir"),
23594        json!({
23595            "a.ts": "a",
23596        }),
23597    )
23598    .await;
23599
23600    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
23601    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23602    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23603
23604    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23605    language_registry.add(Arc::new(Language::new(
23606        LanguageConfig {
23607            name: "TypeScript".into(),
23608            matcher: LanguageMatcher {
23609                path_suffixes: vec!["ts".to_string()],
23610                ..Default::default()
23611            },
23612            ..Default::default()
23613        },
23614        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
23615    )));
23616    let mut fake_language_servers = language_registry.register_fake_lsp(
23617        "TypeScript",
23618        FakeLspAdapter {
23619            capabilities: lsp::ServerCapabilities {
23620                code_lens_provider: Some(lsp::CodeLensOptions {
23621                    resolve_provider: Some(true),
23622                }),
23623                execute_command_provider: Some(lsp::ExecuteCommandOptions {
23624                    commands: vec!["_the/command".to_string()],
23625                    ..lsp::ExecuteCommandOptions::default()
23626                }),
23627                ..lsp::ServerCapabilities::default()
23628            },
23629            ..FakeLspAdapter::default()
23630        },
23631    );
23632
23633    let editor = workspace
23634        .update(cx, |workspace, window, cx| {
23635            workspace.open_abs_path(
23636                PathBuf::from(path!("/dir/a.ts")),
23637                OpenOptions::default(),
23638                window,
23639                cx,
23640            )
23641        })
23642        .unwrap()
23643        .await
23644        .unwrap()
23645        .downcast::<Editor>()
23646        .unwrap();
23647    cx.executor().run_until_parked();
23648
23649    let fake_server = fake_language_servers.next().await.unwrap();
23650
23651    let buffer = editor.update(cx, |editor, cx| {
23652        editor
23653            .buffer()
23654            .read(cx)
23655            .as_singleton()
23656            .expect("have opened a single file by path")
23657    });
23658
23659    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
23660    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
23661    drop(buffer_snapshot);
23662    let actions = cx
23663        .update_window(*workspace, |_, window, cx| {
23664            project.code_actions(&buffer, anchor..anchor, window, cx)
23665        })
23666        .unwrap();
23667
23668    fake_server
23669        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23670            Ok(Some(vec![
23671                lsp::CodeLens {
23672                    range: lsp::Range::default(),
23673                    command: Some(lsp::Command {
23674                        title: "Code lens command".to_owned(),
23675                        command: "_the/command".to_owned(),
23676                        arguments: None,
23677                    }),
23678                    data: None,
23679                },
23680                lsp::CodeLens {
23681                    range: lsp::Range::default(),
23682                    command: Some(lsp::Command {
23683                        title: "Command not in capabilities".to_owned(),
23684                        command: "not in capabilities".to_owned(),
23685                        arguments: None,
23686                    }),
23687                    data: None,
23688                },
23689                lsp::CodeLens {
23690                    range: lsp::Range {
23691                        start: lsp::Position {
23692                            line: 1,
23693                            character: 1,
23694                        },
23695                        end: lsp::Position {
23696                            line: 1,
23697                            character: 1,
23698                        },
23699                    },
23700                    command: Some(lsp::Command {
23701                        title: "Command not in range".to_owned(),
23702                        command: "_the/command".to_owned(),
23703                        arguments: None,
23704                    }),
23705                    data: None,
23706                },
23707            ]))
23708        })
23709        .next()
23710        .await;
23711
23712    let actions = actions.await.unwrap();
23713    assert_eq!(
23714        actions.len(),
23715        1,
23716        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
23717    );
23718    let action = actions[0].clone();
23719    let apply = project.update(cx, |project, cx| {
23720        project.apply_code_action(buffer.clone(), action, true, cx)
23721    });
23722
23723    // Resolving the code action does not populate its edits. In absence of
23724    // edits, we must execute the given command.
23725    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
23726        |mut lens, _| async move {
23727            let lens_command = lens.command.as_mut().expect("should have a command");
23728            assert_eq!(lens_command.title, "Code lens command");
23729            lens_command.arguments = Some(vec![json!("the-argument")]);
23730            Ok(lens)
23731        },
23732    );
23733
23734    // While executing the command, the language server sends the editor
23735    // a `workspaceEdit` request.
23736    fake_server
23737        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
23738            let fake = fake_server.clone();
23739            move |params, _| {
23740                assert_eq!(params.command, "_the/command");
23741                let fake = fake.clone();
23742                async move {
23743                    fake.server
23744                        .request::<lsp::request::ApplyWorkspaceEdit>(
23745                            lsp::ApplyWorkspaceEditParams {
23746                                label: None,
23747                                edit: lsp::WorkspaceEdit {
23748                                    changes: Some(
23749                                        [(
23750                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
23751                                            vec![lsp::TextEdit {
23752                                                range: lsp::Range::new(
23753                                                    lsp::Position::new(0, 0),
23754                                                    lsp::Position::new(0, 0),
23755                                                ),
23756                                                new_text: "X".into(),
23757                                            }],
23758                                        )]
23759                                        .into_iter()
23760                                        .collect(),
23761                                    ),
23762                                    ..lsp::WorkspaceEdit::default()
23763                                },
23764                            },
23765                        )
23766                        .await
23767                        .into_response()
23768                        .unwrap();
23769                    Ok(Some(json!(null)))
23770                }
23771            }
23772        })
23773        .next()
23774        .await;
23775
23776    // Applying the code lens command returns a project transaction containing the edits
23777    // sent by the language server in its `workspaceEdit` request.
23778    let transaction = apply.await.unwrap();
23779    assert!(transaction.0.contains_key(&buffer));
23780    buffer.update(cx, |buffer, cx| {
23781        assert_eq!(buffer.text(), "Xa");
23782        buffer.undo(cx);
23783        assert_eq!(buffer.text(), "a");
23784    });
23785
23786    let actions_after_edits = cx
23787        .update_window(*workspace, |_, window, cx| {
23788            project.code_actions(&buffer, anchor..anchor, window, cx)
23789        })
23790        .unwrap()
23791        .await
23792        .unwrap();
23793    assert_eq!(
23794        actions, actions_after_edits,
23795        "For the same selection, same code lens actions should be returned"
23796    );
23797
23798    let _responses =
23799        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23800            panic!("No more code lens requests are expected");
23801        });
23802    editor.update_in(cx, |editor, window, cx| {
23803        editor.select_all(&SelectAll, window, cx);
23804    });
23805    cx.executor().run_until_parked();
23806    let new_actions = cx
23807        .update_window(*workspace, |_, window, cx| {
23808            project.code_actions(&buffer, anchor..anchor, window, cx)
23809        })
23810        .unwrap()
23811        .await
23812        .unwrap();
23813    assert_eq!(
23814        actions, new_actions,
23815        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
23816    );
23817}
23818
23819#[gpui::test]
23820async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
23821    init_test(cx, |_| {});
23822
23823    let fs = FakeFs::new(cx.executor());
23824    let main_text = r#"fn main() {
23825println!("1");
23826println!("2");
23827println!("3");
23828println!("4");
23829println!("5");
23830}"#;
23831    let lib_text = "mod foo {}";
23832    fs.insert_tree(
23833        path!("/a"),
23834        json!({
23835            "lib.rs": lib_text,
23836            "main.rs": main_text,
23837        }),
23838    )
23839    .await;
23840
23841    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23842    let (workspace, cx) =
23843        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23844    let worktree_id = workspace.update(cx, |workspace, cx| {
23845        workspace.project().update(cx, |project, cx| {
23846            project.worktrees(cx).next().unwrap().read(cx).id()
23847        })
23848    });
23849
23850    let expected_ranges = vec![
23851        Point::new(0, 0)..Point::new(0, 0),
23852        Point::new(1, 0)..Point::new(1, 1),
23853        Point::new(2, 0)..Point::new(2, 2),
23854        Point::new(3, 0)..Point::new(3, 3),
23855    ];
23856
23857    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23858    let editor_1 = workspace
23859        .update_in(cx, |workspace, window, cx| {
23860            workspace.open_path(
23861                (worktree_id, rel_path("main.rs")),
23862                Some(pane_1.downgrade()),
23863                true,
23864                window,
23865                cx,
23866            )
23867        })
23868        .unwrap()
23869        .await
23870        .downcast::<Editor>()
23871        .unwrap();
23872    pane_1.update(cx, |pane, cx| {
23873        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23874        open_editor.update(cx, |editor, cx| {
23875            assert_eq!(
23876                editor.display_text(cx),
23877                main_text,
23878                "Original main.rs text on initial open",
23879            );
23880            assert_eq!(
23881                editor
23882                    .selections
23883                    .all::<Point>(&editor.display_snapshot(cx))
23884                    .into_iter()
23885                    .map(|s| s.range())
23886                    .collect::<Vec<_>>(),
23887                vec![Point::zero()..Point::zero()],
23888                "Default selections on initial open",
23889            );
23890        })
23891    });
23892    editor_1.update_in(cx, |editor, window, cx| {
23893        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23894            s.select_ranges(expected_ranges.clone());
23895        });
23896    });
23897
23898    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
23899        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
23900    });
23901    let editor_2 = workspace
23902        .update_in(cx, |workspace, window, cx| {
23903            workspace.open_path(
23904                (worktree_id, rel_path("main.rs")),
23905                Some(pane_2.downgrade()),
23906                true,
23907                window,
23908                cx,
23909            )
23910        })
23911        .unwrap()
23912        .await
23913        .downcast::<Editor>()
23914        .unwrap();
23915    pane_2.update(cx, |pane, cx| {
23916        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23917        open_editor.update(cx, |editor, cx| {
23918            assert_eq!(
23919                editor.display_text(cx),
23920                main_text,
23921                "Original main.rs text on initial open in another panel",
23922            );
23923            assert_eq!(
23924                editor
23925                    .selections
23926                    .all::<Point>(&editor.display_snapshot(cx))
23927                    .into_iter()
23928                    .map(|s| s.range())
23929                    .collect::<Vec<_>>(),
23930                vec![Point::zero()..Point::zero()],
23931                "Default selections on initial open in another panel",
23932            );
23933        })
23934    });
23935
23936    editor_2.update_in(cx, |editor, window, cx| {
23937        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
23938    });
23939
23940    let _other_editor_1 = workspace
23941        .update_in(cx, |workspace, window, cx| {
23942            workspace.open_path(
23943                (worktree_id, rel_path("lib.rs")),
23944                Some(pane_1.downgrade()),
23945                true,
23946                window,
23947                cx,
23948            )
23949        })
23950        .unwrap()
23951        .await
23952        .downcast::<Editor>()
23953        .unwrap();
23954    pane_1
23955        .update_in(cx, |pane, window, cx| {
23956            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23957        })
23958        .await
23959        .unwrap();
23960    drop(editor_1);
23961    pane_1.update(cx, |pane, cx| {
23962        pane.active_item()
23963            .unwrap()
23964            .downcast::<Editor>()
23965            .unwrap()
23966            .update(cx, |editor, cx| {
23967                assert_eq!(
23968                    editor.display_text(cx),
23969                    lib_text,
23970                    "Other file should be open and active",
23971                );
23972            });
23973        assert_eq!(pane.items().count(), 1, "No other editors should be open");
23974    });
23975
23976    let _other_editor_2 = workspace
23977        .update_in(cx, |workspace, window, cx| {
23978            workspace.open_path(
23979                (worktree_id, rel_path("lib.rs")),
23980                Some(pane_2.downgrade()),
23981                true,
23982                window,
23983                cx,
23984            )
23985        })
23986        .unwrap()
23987        .await
23988        .downcast::<Editor>()
23989        .unwrap();
23990    pane_2
23991        .update_in(cx, |pane, window, cx| {
23992            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23993        })
23994        .await
23995        .unwrap();
23996    drop(editor_2);
23997    pane_2.update(cx, |pane, cx| {
23998        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23999        open_editor.update(cx, |editor, cx| {
24000            assert_eq!(
24001                editor.display_text(cx),
24002                lib_text,
24003                "Other file should be open and active in another panel too",
24004            );
24005        });
24006        assert_eq!(
24007            pane.items().count(),
24008            1,
24009            "No other editors should be open in another pane",
24010        );
24011    });
24012
24013    let _editor_1_reopened = workspace
24014        .update_in(cx, |workspace, window, cx| {
24015            workspace.open_path(
24016                (worktree_id, rel_path("main.rs")),
24017                Some(pane_1.downgrade()),
24018                true,
24019                window,
24020                cx,
24021            )
24022        })
24023        .unwrap()
24024        .await
24025        .downcast::<Editor>()
24026        .unwrap();
24027    let _editor_2_reopened = workspace
24028        .update_in(cx, |workspace, window, cx| {
24029            workspace.open_path(
24030                (worktree_id, rel_path("main.rs")),
24031                Some(pane_2.downgrade()),
24032                true,
24033                window,
24034                cx,
24035            )
24036        })
24037        .unwrap()
24038        .await
24039        .downcast::<Editor>()
24040        .unwrap();
24041    pane_1.update(cx, |pane, cx| {
24042        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24043        open_editor.update(cx, |editor, cx| {
24044            assert_eq!(
24045                editor.display_text(cx),
24046                main_text,
24047                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
24048            );
24049            assert_eq!(
24050                editor
24051                    .selections
24052                    .all::<Point>(&editor.display_snapshot(cx))
24053                    .into_iter()
24054                    .map(|s| s.range())
24055                    .collect::<Vec<_>>(),
24056                expected_ranges,
24057                "Previous editor in the 1st panel had selections and should get them restored on reopen",
24058            );
24059        })
24060    });
24061    pane_2.update(cx, |pane, cx| {
24062        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24063        open_editor.update(cx, |editor, cx| {
24064            assert_eq!(
24065                editor.display_text(cx),
24066                r#"fn main() {
24067⋯rintln!("1");
24068⋯intln!("2");
24069⋯ntln!("3");
24070println!("4");
24071println!("5");
24072}"#,
24073                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
24074            );
24075            assert_eq!(
24076                editor
24077                    .selections
24078                    .all::<Point>(&editor.display_snapshot(cx))
24079                    .into_iter()
24080                    .map(|s| s.range())
24081                    .collect::<Vec<_>>(),
24082                vec![Point::zero()..Point::zero()],
24083                "Previous editor in the 2nd pane had no selections changed hence should restore none",
24084            );
24085        })
24086    });
24087}
24088
24089#[gpui::test]
24090async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
24091    init_test(cx, |_| {});
24092
24093    let fs = FakeFs::new(cx.executor());
24094    let main_text = r#"fn main() {
24095println!("1");
24096println!("2");
24097println!("3");
24098println!("4");
24099println!("5");
24100}"#;
24101    let lib_text = "mod foo {}";
24102    fs.insert_tree(
24103        path!("/a"),
24104        json!({
24105            "lib.rs": lib_text,
24106            "main.rs": main_text,
24107        }),
24108    )
24109    .await;
24110
24111    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24112    let (workspace, cx) =
24113        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24114    let worktree_id = workspace.update(cx, |workspace, cx| {
24115        workspace.project().update(cx, |project, cx| {
24116            project.worktrees(cx).next().unwrap().read(cx).id()
24117        })
24118    });
24119
24120    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24121    let editor = workspace
24122        .update_in(cx, |workspace, window, cx| {
24123            workspace.open_path(
24124                (worktree_id, rel_path("main.rs")),
24125                Some(pane.downgrade()),
24126                true,
24127                window,
24128                cx,
24129            )
24130        })
24131        .unwrap()
24132        .await
24133        .downcast::<Editor>()
24134        .unwrap();
24135    pane.update(cx, |pane, cx| {
24136        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24137        open_editor.update(cx, |editor, cx| {
24138            assert_eq!(
24139                editor.display_text(cx),
24140                main_text,
24141                "Original main.rs text on initial open",
24142            );
24143        })
24144    });
24145    editor.update_in(cx, |editor, window, cx| {
24146        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
24147    });
24148
24149    cx.update_global(|store: &mut SettingsStore, cx| {
24150        store.update_user_settings(cx, |s| {
24151            s.workspace.restore_on_file_reopen = Some(false);
24152        });
24153    });
24154    editor.update_in(cx, |editor, window, cx| {
24155        editor.fold_ranges(
24156            vec![
24157                Point::new(1, 0)..Point::new(1, 1),
24158                Point::new(2, 0)..Point::new(2, 2),
24159                Point::new(3, 0)..Point::new(3, 3),
24160            ],
24161            false,
24162            window,
24163            cx,
24164        );
24165    });
24166    pane.update_in(cx, |pane, window, cx| {
24167        pane.close_all_items(&CloseAllItems::default(), window, cx)
24168    })
24169    .await
24170    .unwrap();
24171    pane.update(cx, |pane, _| {
24172        assert!(pane.active_item().is_none());
24173    });
24174    cx.update_global(|store: &mut SettingsStore, cx| {
24175        store.update_user_settings(cx, |s| {
24176            s.workspace.restore_on_file_reopen = Some(true);
24177        });
24178    });
24179
24180    let _editor_reopened = workspace
24181        .update_in(cx, |workspace, window, cx| {
24182            workspace.open_path(
24183                (worktree_id, rel_path("main.rs")),
24184                Some(pane.downgrade()),
24185                true,
24186                window,
24187                cx,
24188            )
24189        })
24190        .unwrap()
24191        .await
24192        .downcast::<Editor>()
24193        .unwrap();
24194    pane.update(cx, |pane, cx| {
24195        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24196        open_editor.update(cx, |editor, cx| {
24197            assert_eq!(
24198                editor.display_text(cx),
24199                main_text,
24200                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
24201            );
24202        })
24203    });
24204}
24205
24206#[gpui::test]
24207async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
24208    struct EmptyModalView {
24209        focus_handle: gpui::FocusHandle,
24210    }
24211    impl EventEmitter<DismissEvent> for EmptyModalView {}
24212    impl Render for EmptyModalView {
24213        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
24214            div()
24215        }
24216    }
24217    impl Focusable for EmptyModalView {
24218        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
24219            self.focus_handle.clone()
24220        }
24221    }
24222    impl workspace::ModalView for EmptyModalView {}
24223    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
24224        EmptyModalView {
24225            focus_handle: cx.focus_handle(),
24226        }
24227    }
24228
24229    init_test(cx, |_| {});
24230
24231    let fs = FakeFs::new(cx.executor());
24232    let project = Project::test(fs, [], cx).await;
24233    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24234    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
24235    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24236    let editor = cx.new_window_entity(|window, cx| {
24237        Editor::new(
24238            EditorMode::full(),
24239            buffer,
24240            Some(project.clone()),
24241            window,
24242            cx,
24243        )
24244    });
24245    workspace
24246        .update(cx, |workspace, window, cx| {
24247            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
24248        })
24249        .unwrap();
24250    editor.update_in(cx, |editor, window, cx| {
24251        editor.open_context_menu(&OpenContextMenu, window, cx);
24252        assert!(editor.mouse_context_menu.is_some());
24253    });
24254    workspace
24255        .update(cx, |workspace, window, cx| {
24256            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
24257        })
24258        .unwrap();
24259    cx.read(|cx| {
24260        assert!(editor.read(cx).mouse_context_menu.is_none());
24261    });
24262}
24263
24264fn set_linked_edit_ranges(
24265    opening: (Point, Point),
24266    closing: (Point, Point),
24267    editor: &mut Editor,
24268    cx: &mut Context<Editor>,
24269) {
24270    let Some((buffer, _)) = editor
24271        .buffer
24272        .read(cx)
24273        .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
24274    else {
24275        panic!("Failed to get buffer for selection position");
24276    };
24277    let buffer = buffer.read(cx);
24278    let buffer_id = buffer.remote_id();
24279    let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
24280    let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
24281    let mut linked_ranges = HashMap::default();
24282    linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
24283    editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
24284}
24285
24286#[gpui::test]
24287async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
24288    init_test(cx, |_| {});
24289
24290    let fs = FakeFs::new(cx.executor());
24291    fs.insert_file(path!("/file.html"), Default::default())
24292        .await;
24293
24294    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
24295
24296    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24297    let html_language = Arc::new(Language::new(
24298        LanguageConfig {
24299            name: "HTML".into(),
24300            matcher: LanguageMatcher {
24301                path_suffixes: vec!["html".to_string()],
24302                ..LanguageMatcher::default()
24303            },
24304            brackets: BracketPairConfig {
24305                pairs: vec![BracketPair {
24306                    start: "<".into(),
24307                    end: ">".into(),
24308                    close: true,
24309                    ..Default::default()
24310                }],
24311                ..Default::default()
24312            },
24313            ..Default::default()
24314        },
24315        Some(tree_sitter_html::LANGUAGE.into()),
24316    ));
24317    language_registry.add(html_language);
24318    let mut fake_servers = language_registry.register_fake_lsp(
24319        "HTML",
24320        FakeLspAdapter {
24321            capabilities: lsp::ServerCapabilities {
24322                completion_provider: Some(lsp::CompletionOptions {
24323                    resolve_provider: Some(true),
24324                    ..Default::default()
24325                }),
24326                ..Default::default()
24327            },
24328            ..Default::default()
24329        },
24330    );
24331
24332    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24333    let cx = &mut VisualTestContext::from_window(*workspace, cx);
24334
24335    let worktree_id = workspace
24336        .update(cx, |workspace, _window, cx| {
24337            workspace.project().update(cx, |project, cx| {
24338                project.worktrees(cx).next().unwrap().read(cx).id()
24339            })
24340        })
24341        .unwrap();
24342    project
24343        .update(cx, |project, cx| {
24344            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
24345        })
24346        .await
24347        .unwrap();
24348    let editor = workspace
24349        .update(cx, |workspace, window, cx| {
24350            workspace.open_path((worktree_id, rel_path("file.html")), None, true, window, cx)
24351        })
24352        .unwrap()
24353        .await
24354        .unwrap()
24355        .downcast::<Editor>()
24356        .unwrap();
24357
24358    let fake_server = fake_servers.next().await.unwrap();
24359    editor.update_in(cx, |editor, window, cx| {
24360        editor.set_text("<ad></ad>", window, cx);
24361        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24362            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
24363        });
24364        set_linked_edit_ranges(
24365            (Point::new(0, 1), Point::new(0, 3)),
24366            (Point::new(0, 6), Point::new(0, 8)),
24367            editor,
24368            cx,
24369        );
24370    });
24371    let mut completion_handle =
24372        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
24373            Ok(Some(lsp::CompletionResponse::Array(vec![
24374                lsp::CompletionItem {
24375                    label: "head".to_string(),
24376                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
24377                        lsp::InsertReplaceEdit {
24378                            new_text: "head".to_string(),
24379                            insert: lsp::Range::new(
24380                                lsp::Position::new(0, 1),
24381                                lsp::Position::new(0, 3),
24382                            ),
24383                            replace: lsp::Range::new(
24384                                lsp::Position::new(0, 1),
24385                                lsp::Position::new(0, 3),
24386                            ),
24387                        },
24388                    )),
24389                    ..Default::default()
24390                },
24391            ])))
24392        });
24393    editor.update_in(cx, |editor, window, cx| {
24394        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
24395    });
24396    cx.run_until_parked();
24397    completion_handle.next().await.unwrap();
24398    editor.update(cx, |editor, _| {
24399        assert!(
24400            editor.context_menu_visible(),
24401            "Completion menu should be visible"
24402        );
24403    });
24404    editor.update_in(cx, |editor, window, cx| {
24405        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
24406    });
24407    cx.executor().run_until_parked();
24408    editor.update(cx, |editor, cx| {
24409        assert_eq!(editor.text(cx), "<head></head>");
24410    });
24411}
24412
24413#[gpui::test]
24414async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
24415    init_test(cx, |_| {});
24416
24417    let mut cx = EditorTestContext::new(cx).await;
24418    let language = Arc::new(Language::new(
24419        LanguageConfig {
24420            name: "TSX".into(),
24421            matcher: LanguageMatcher {
24422                path_suffixes: vec!["tsx".to_string()],
24423                ..LanguageMatcher::default()
24424            },
24425            brackets: BracketPairConfig {
24426                pairs: vec![BracketPair {
24427                    start: "<".into(),
24428                    end: ">".into(),
24429                    close: true,
24430                    ..Default::default()
24431                }],
24432                ..Default::default()
24433            },
24434            linked_edit_characters: HashSet::from_iter(['.']),
24435            ..Default::default()
24436        },
24437        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
24438    ));
24439    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24440
24441    // Test typing > does not extend linked pair
24442    cx.set_state("<divˇ<div></div>");
24443    cx.update_editor(|editor, _, cx| {
24444        set_linked_edit_ranges(
24445            (Point::new(0, 1), Point::new(0, 4)),
24446            (Point::new(0, 11), Point::new(0, 14)),
24447            editor,
24448            cx,
24449        );
24450    });
24451    cx.update_editor(|editor, window, cx| {
24452        editor.handle_input(">", window, cx);
24453    });
24454    cx.assert_editor_state("<div>ˇ<div></div>");
24455
24456    // Test typing . do extend linked pair
24457    cx.set_state("<Animatedˇ></Animated>");
24458    cx.update_editor(|editor, _, cx| {
24459        set_linked_edit_ranges(
24460            (Point::new(0, 1), Point::new(0, 9)),
24461            (Point::new(0, 12), Point::new(0, 20)),
24462            editor,
24463            cx,
24464        );
24465    });
24466    cx.update_editor(|editor, window, cx| {
24467        editor.handle_input(".", window, cx);
24468    });
24469    cx.assert_editor_state("<Animated.ˇ></Animated.>");
24470    cx.update_editor(|editor, _, cx| {
24471        set_linked_edit_ranges(
24472            (Point::new(0, 1), Point::new(0, 10)),
24473            (Point::new(0, 13), Point::new(0, 21)),
24474            editor,
24475            cx,
24476        );
24477    });
24478    cx.update_editor(|editor, window, cx| {
24479        editor.handle_input("V", window, cx);
24480    });
24481    cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
24482}
24483
24484#[gpui::test]
24485async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
24486    init_test(cx, |_| {});
24487
24488    let fs = FakeFs::new(cx.executor());
24489    fs.insert_tree(
24490        path!("/root"),
24491        json!({
24492            "a": {
24493                "main.rs": "fn main() {}",
24494            },
24495            "foo": {
24496                "bar": {
24497                    "external_file.rs": "pub mod external {}",
24498                }
24499            }
24500        }),
24501    )
24502    .await;
24503
24504    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
24505    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24506    language_registry.add(rust_lang());
24507    let _fake_servers = language_registry.register_fake_lsp(
24508        "Rust",
24509        FakeLspAdapter {
24510            ..FakeLspAdapter::default()
24511        },
24512    );
24513    let (workspace, cx) =
24514        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24515    let worktree_id = workspace.update(cx, |workspace, cx| {
24516        workspace.project().update(cx, |project, cx| {
24517            project.worktrees(cx).next().unwrap().read(cx).id()
24518        })
24519    });
24520
24521    let assert_language_servers_count =
24522        |expected: usize, context: &str, cx: &mut VisualTestContext| {
24523            project.update(cx, |project, cx| {
24524                let current = project
24525                    .lsp_store()
24526                    .read(cx)
24527                    .as_local()
24528                    .unwrap()
24529                    .language_servers
24530                    .len();
24531                assert_eq!(expected, current, "{context}");
24532            });
24533        };
24534
24535    assert_language_servers_count(
24536        0,
24537        "No servers should be running before any file is open",
24538        cx,
24539    );
24540    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24541    let main_editor = workspace
24542        .update_in(cx, |workspace, window, cx| {
24543            workspace.open_path(
24544                (worktree_id, rel_path("main.rs")),
24545                Some(pane.downgrade()),
24546                true,
24547                window,
24548                cx,
24549            )
24550        })
24551        .unwrap()
24552        .await
24553        .downcast::<Editor>()
24554        .unwrap();
24555    pane.update(cx, |pane, cx| {
24556        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24557        open_editor.update(cx, |editor, cx| {
24558            assert_eq!(
24559                editor.display_text(cx),
24560                "fn main() {}",
24561                "Original main.rs text on initial open",
24562            );
24563        });
24564        assert_eq!(open_editor, main_editor);
24565    });
24566    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
24567
24568    let external_editor = workspace
24569        .update_in(cx, |workspace, window, cx| {
24570            workspace.open_abs_path(
24571                PathBuf::from("/root/foo/bar/external_file.rs"),
24572                OpenOptions::default(),
24573                window,
24574                cx,
24575            )
24576        })
24577        .await
24578        .expect("opening external file")
24579        .downcast::<Editor>()
24580        .expect("downcasted external file's open element to editor");
24581    pane.update(cx, |pane, cx| {
24582        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24583        open_editor.update(cx, |editor, cx| {
24584            assert_eq!(
24585                editor.display_text(cx),
24586                "pub mod external {}",
24587                "External file is open now",
24588            );
24589        });
24590        assert_eq!(open_editor, external_editor);
24591    });
24592    assert_language_servers_count(
24593        1,
24594        "Second, external, *.rs file should join the existing server",
24595        cx,
24596    );
24597
24598    pane.update_in(cx, |pane, window, cx| {
24599        pane.close_active_item(&CloseActiveItem::default(), window, cx)
24600    })
24601    .await
24602    .unwrap();
24603    pane.update_in(cx, |pane, window, cx| {
24604        pane.navigate_backward(&Default::default(), window, cx);
24605    });
24606    cx.run_until_parked();
24607    pane.update(cx, |pane, cx| {
24608        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24609        open_editor.update(cx, |editor, cx| {
24610            assert_eq!(
24611                editor.display_text(cx),
24612                "pub mod external {}",
24613                "External file is open now",
24614            );
24615        });
24616    });
24617    assert_language_servers_count(
24618        1,
24619        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
24620        cx,
24621    );
24622
24623    cx.update(|_, cx| {
24624        workspace::reload(cx);
24625    });
24626    assert_language_servers_count(
24627        1,
24628        "After reloading the worktree with local and external files opened, only one project should be started",
24629        cx,
24630    );
24631}
24632
24633#[gpui::test]
24634async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
24635    init_test(cx, |_| {});
24636
24637    let mut cx = EditorTestContext::new(cx).await;
24638    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24639    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24640
24641    // test cursor move to start of each line on tab
24642    // for `if`, `elif`, `else`, `while`, `with` and `for`
24643    cx.set_state(indoc! {"
24644        def main():
24645        ˇ    for item in items:
24646        ˇ        while item.active:
24647        ˇ            if item.value > 10:
24648        ˇ                continue
24649        ˇ            elif item.value < 0:
24650        ˇ                break
24651        ˇ            else:
24652        ˇ                with item.context() as ctx:
24653        ˇ                    yield count
24654        ˇ        else:
24655        ˇ            log('while else')
24656        ˇ    else:
24657        ˇ        log('for else')
24658    "});
24659    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24660    cx.assert_editor_state(indoc! {"
24661        def main():
24662            ˇfor item in items:
24663                ˇwhile item.active:
24664                    ˇif item.value > 10:
24665                        ˇcontinue
24666                    ˇelif item.value < 0:
24667                        ˇbreak
24668                    ˇelse:
24669                        ˇwith item.context() as ctx:
24670                            ˇyield count
24671                ˇelse:
24672                    ˇlog('while else')
24673            ˇelse:
24674                ˇlog('for else')
24675    "});
24676    // test relative indent is preserved when tab
24677    // for `if`, `elif`, `else`, `while`, `with` and `for`
24678    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24679    cx.assert_editor_state(indoc! {"
24680        def main():
24681                ˇfor item in items:
24682                    ˇwhile item.active:
24683                        ˇif item.value > 10:
24684                            ˇcontinue
24685                        ˇelif item.value < 0:
24686                            ˇbreak
24687                        ˇelse:
24688                            ˇwith item.context() as ctx:
24689                                ˇyield count
24690                    ˇelse:
24691                        ˇlog('while else')
24692                ˇelse:
24693                    ˇlog('for else')
24694    "});
24695
24696    // test cursor move to start of each line on tab
24697    // for `try`, `except`, `else`, `finally`, `match` and `def`
24698    cx.set_state(indoc! {"
24699        def main():
24700        ˇ    try:
24701        ˇ        fetch()
24702        ˇ    except ValueError:
24703        ˇ        handle_error()
24704        ˇ    else:
24705        ˇ        match value:
24706        ˇ            case _:
24707        ˇ    finally:
24708        ˇ        def status():
24709        ˇ            return 0
24710    "});
24711    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24712    cx.assert_editor_state(indoc! {"
24713        def main():
24714            ˇtry:
24715                ˇfetch()
24716            ˇexcept ValueError:
24717                ˇhandle_error()
24718            ˇelse:
24719                ˇmatch value:
24720                    ˇcase _:
24721            ˇfinally:
24722                ˇdef status():
24723                    ˇreturn 0
24724    "});
24725    // test relative indent is preserved when tab
24726    // for `try`, `except`, `else`, `finally`, `match` and `def`
24727    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24728    cx.assert_editor_state(indoc! {"
24729        def main():
24730                ˇtry:
24731                    ˇfetch()
24732                ˇexcept ValueError:
24733                    ˇhandle_error()
24734                ˇelse:
24735                    ˇmatch value:
24736                        ˇcase _:
24737                ˇfinally:
24738                    ˇdef status():
24739                        ˇreturn 0
24740    "});
24741}
24742
24743#[gpui::test]
24744async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
24745    init_test(cx, |_| {});
24746
24747    let mut cx = EditorTestContext::new(cx).await;
24748    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24749    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24750
24751    // test `else` auto outdents when typed inside `if` block
24752    cx.set_state(indoc! {"
24753        def main():
24754            if i == 2:
24755                return
24756                ˇ
24757    "});
24758    cx.update_editor(|editor, window, cx| {
24759        editor.handle_input("else:", window, cx);
24760    });
24761    cx.assert_editor_state(indoc! {"
24762        def main():
24763            if i == 2:
24764                return
24765            else:ˇ
24766    "});
24767
24768    // test `except` auto outdents when typed inside `try` block
24769    cx.set_state(indoc! {"
24770        def main():
24771            try:
24772                i = 2
24773                ˇ
24774    "});
24775    cx.update_editor(|editor, window, cx| {
24776        editor.handle_input("except:", window, cx);
24777    });
24778    cx.assert_editor_state(indoc! {"
24779        def main():
24780            try:
24781                i = 2
24782            except:ˇ
24783    "});
24784
24785    // test `else` auto outdents when typed inside `except` block
24786    cx.set_state(indoc! {"
24787        def main():
24788            try:
24789                i = 2
24790            except:
24791                j = 2
24792                ˇ
24793    "});
24794    cx.update_editor(|editor, window, cx| {
24795        editor.handle_input("else:", window, cx);
24796    });
24797    cx.assert_editor_state(indoc! {"
24798        def main():
24799            try:
24800                i = 2
24801            except:
24802                j = 2
24803            else:ˇ
24804    "});
24805
24806    // test `finally` auto outdents when typed inside `else` block
24807    cx.set_state(indoc! {"
24808        def main():
24809            try:
24810                i = 2
24811            except:
24812                j = 2
24813            else:
24814                k = 2
24815                ˇ
24816    "});
24817    cx.update_editor(|editor, window, cx| {
24818        editor.handle_input("finally:", window, cx);
24819    });
24820    cx.assert_editor_state(indoc! {"
24821        def main():
24822            try:
24823                i = 2
24824            except:
24825                j = 2
24826            else:
24827                k = 2
24828            finally:ˇ
24829    "});
24830
24831    // test `else` does not outdents when typed inside `except` block right after for block
24832    cx.set_state(indoc! {"
24833        def main():
24834            try:
24835                i = 2
24836            except:
24837                for i in range(n):
24838                    pass
24839                ˇ
24840    "});
24841    cx.update_editor(|editor, window, cx| {
24842        editor.handle_input("else:", window, cx);
24843    });
24844    cx.assert_editor_state(indoc! {"
24845        def main():
24846            try:
24847                i = 2
24848            except:
24849                for i in range(n):
24850                    pass
24851                else:ˇ
24852    "});
24853
24854    // test `finally` auto outdents when typed inside `else` block right after for block
24855    cx.set_state(indoc! {"
24856        def main():
24857            try:
24858                i = 2
24859            except:
24860                j = 2
24861            else:
24862                for i in range(n):
24863                    pass
24864                ˇ
24865    "});
24866    cx.update_editor(|editor, window, cx| {
24867        editor.handle_input("finally:", window, cx);
24868    });
24869    cx.assert_editor_state(indoc! {"
24870        def main():
24871            try:
24872                i = 2
24873            except:
24874                j = 2
24875            else:
24876                for i in range(n):
24877                    pass
24878            finally:ˇ
24879    "});
24880
24881    // test `except` outdents to inner "try" block
24882    cx.set_state(indoc! {"
24883        def main():
24884            try:
24885                i = 2
24886                if i == 2:
24887                    try:
24888                        i = 3
24889                        ˇ
24890    "});
24891    cx.update_editor(|editor, window, cx| {
24892        editor.handle_input("except:", window, cx);
24893    });
24894    cx.assert_editor_state(indoc! {"
24895        def main():
24896            try:
24897                i = 2
24898                if i == 2:
24899                    try:
24900                        i = 3
24901                    except:ˇ
24902    "});
24903
24904    // test `except` outdents to outer "try" block
24905    cx.set_state(indoc! {"
24906        def main():
24907            try:
24908                i = 2
24909                if i == 2:
24910                    try:
24911                        i = 3
24912                ˇ
24913    "});
24914    cx.update_editor(|editor, window, cx| {
24915        editor.handle_input("except:", window, cx);
24916    });
24917    cx.assert_editor_state(indoc! {"
24918        def main():
24919            try:
24920                i = 2
24921                if i == 2:
24922                    try:
24923                        i = 3
24924            except:ˇ
24925    "});
24926
24927    // test `else` stays at correct indent when typed after `for` block
24928    cx.set_state(indoc! {"
24929        def main():
24930            for i in range(10):
24931                if i == 3:
24932                    break
24933            ˇ
24934    "});
24935    cx.update_editor(|editor, window, cx| {
24936        editor.handle_input("else:", window, cx);
24937    });
24938    cx.assert_editor_state(indoc! {"
24939        def main():
24940            for i in range(10):
24941                if i == 3:
24942                    break
24943            else:ˇ
24944    "});
24945
24946    // test does not outdent on typing after line with square brackets
24947    cx.set_state(indoc! {"
24948        def f() -> list[str]:
24949            ˇ
24950    "});
24951    cx.update_editor(|editor, window, cx| {
24952        editor.handle_input("a", window, cx);
24953    });
24954    cx.assert_editor_state(indoc! {"
24955        def f() -> list[str]:
2495624957    "});
24958
24959    // test does not outdent on typing : after case keyword
24960    cx.set_state(indoc! {"
24961        match 1:
24962            caseˇ
24963    "});
24964    cx.update_editor(|editor, window, cx| {
24965        editor.handle_input(":", window, cx);
24966    });
24967    cx.assert_editor_state(indoc! {"
24968        match 1:
24969            case:ˇ
24970    "});
24971}
24972
24973#[gpui::test]
24974async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
24975    init_test(cx, |_| {});
24976    update_test_language_settings(cx, |settings| {
24977        settings.defaults.extend_comment_on_newline = Some(false);
24978    });
24979    let mut cx = EditorTestContext::new(cx).await;
24980    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24981    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24982
24983    // test correct indent after newline on comment
24984    cx.set_state(indoc! {"
24985        # COMMENT:ˇ
24986    "});
24987    cx.update_editor(|editor, window, cx| {
24988        editor.newline(&Newline, window, cx);
24989    });
24990    cx.assert_editor_state(indoc! {"
24991        # COMMENT:
24992        ˇ
24993    "});
24994
24995    // test correct indent after newline in brackets
24996    cx.set_state(indoc! {"
24997        {ˇ}
24998    "});
24999    cx.update_editor(|editor, window, cx| {
25000        editor.newline(&Newline, window, cx);
25001    });
25002    cx.run_until_parked();
25003    cx.assert_editor_state(indoc! {"
25004        {
25005            ˇ
25006        }
25007    "});
25008
25009    cx.set_state(indoc! {"
25010        (ˇ)
25011    "});
25012    cx.update_editor(|editor, window, cx| {
25013        editor.newline(&Newline, window, cx);
25014    });
25015    cx.run_until_parked();
25016    cx.assert_editor_state(indoc! {"
25017        (
25018            ˇ
25019        )
25020    "});
25021
25022    // do not indent after empty lists or dictionaries
25023    cx.set_state(indoc! {"
25024        a = []ˇ
25025    "});
25026    cx.update_editor(|editor, window, cx| {
25027        editor.newline(&Newline, window, cx);
25028    });
25029    cx.run_until_parked();
25030    cx.assert_editor_state(indoc! {"
25031        a = []
25032        ˇ
25033    "});
25034}
25035
25036#[gpui::test]
25037async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
25038    init_test(cx, |_| {});
25039
25040    let mut cx = EditorTestContext::new(cx).await;
25041    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25042    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25043
25044    // test cursor move to start of each line on tab
25045    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
25046    cx.set_state(indoc! {"
25047        function main() {
25048        ˇ    for item in $items; do
25049        ˇ        while [ -n \"$item\" ]; do
25050        ˇ            if [ \"$value\" -gt 10 ]; then
25051        ˇ                continue
25052        ˇ            elif [ \"$value\" -lt 0 ]; then
25053        ˇ                break
25054        ˇ            else
25055        ˇ                echo \"$item\"
25056        ˇ            fi
25057        ˇ        done
25058        ˇ    done
25059        ˇ}
25060    "});
25061    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25062    cx.assert_editor_state(indoc! {"
25063        function main() {
25064            ˇfor item in $items; do
25065                ˇwhile [ -n \"$item\" ]; do
25066                    ˇif [ \"$value\" -gt 10 ]; then
25067                        ˇcontinue
25068                    ˇelif [ \"$value\" -lt 0 ]; then
25069                        ˇbreak
25070                    ˇelse
25071                        ˇecho \"$item\"
25072                    ˇfi
25073                ˇdone
25074            ˇdone
25075        ˇ}
25076    "});
25077    // test relative indent is preserved when tab
25078    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25079    cx.assert_editor_state(indoc! {"
25080        function main() {
25081                ˇfor item in $items; do
25082                    ˇwhile [ -n \"$item\" ]; do
25083                        ˇif [ \"$value\" -gt 10 ]; then
25084                            ˇcontinue
25085                        ˇelif [ \"$value\" -lt 0 ]; then
25086                            ˇbreak
25087                        ˇelse
25088                            ˇecho \"$item\"
25089                        ˇfi
25090                    ˇdone
25091                ˇdone
25092            ˇ}
25093    "});
25094
25095    // test cursor move to start of each line on tab
25096    // for `case` statement with patterns
25097    cx.set_state(indoc! {"
25098        function handle() {
25099        ˇ    case \"$1\" in
25100        ˇ        start)
25101        ˇ            echo \"a\"
25102        ˇ            ;;
25103        ˇ        stop)
25104        ˇ            echo \"b\"
25105        ˇ            ;;
25106        ˇ        *)
25107        ˇ            echo \"c\"
25108        ˇ            ;;
25109        ˇ    esac
25110        ˇ}
25111    "});
25112    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25113    cx.assert_editor_state(indoc! {"
25114        function handle() {
25115            ˇcase \"$1\" in
25116                ˇstart)
25117                    ˇecho \"a\"
25118                    ˇ;;
25119                ˇstop)
25120                    ˇecho \"b\"
25121                    ˇ;;
25122                ˇ*)
25123                    ˇecho \"c\"
25124                    ˇ;;
25125            ˇesac
25126        ˇ}
25127    "});
25128}
25129
25130#[gpui::test]
25131async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
25132    init_test(cx, |_| {});
25133
25134    let mut cx = EditorTestContext::new(cx).await;
25135    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25136    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25137
25138    // test indents on comment insert
25139    cx.set_state(indoc! {"
25140        function main() {
25141        ˇ    for item in $items; do
25142        ˇ        while [ -n \"$item\" ]; do
25143        ˇ            if [ \"$value\" -gt 10 ]; then
25144        ˇ                continue
25145        ˇ            elif [ \"$value\" -lt 0 ]; then
25146        ˇ                break
25147        ˇ            else
25148        ˇ                echo \"$item\"
25149        ˇ            fi
25150        ˇ        done
25151        ˇ    done
25152        ˇ}
25153    "});
25154    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
25155    cx.assert_editor_state(indoc! {"
25156        function main() {
25157        #ˇ    for item in $items; do
25158        #ˇ        while [ -n \"$item\" ]; do
25159        #ˇ            if [ \"$value\" -gt 10 ]; then
25160        #ˇ                continue
25161        #ˇ            elif [ \"$value\" -lt 0 ]; then
25162        #ˇ                break
25163        #ˇ            else
25164        #ˇ                echo \"$item\"
25165        #ˇ            fi
25166        #ˇ        done
25167        #ˇ    done
25168        #ˇ}
25169    "});
25170}
25171
25172#[gpui::test]
25173async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
25174    init_test(cx, |_| {});
25175
25176    let mut cx = EditorTestContext::new(cx).await;
25177    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25178    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25179
25180    // test `else` auto outdents when typed inside `if` block
25181    cx.set_state(indoc! {"
25182        if [ \"$1\" = \"test\" ]; then
25183            echo \"foo bar\"
25184            ˇ
25185    "});
25186    cx.update_editor(|editor, window, cx| {
25187        editor.handle_input("else", window, cx);
25188    });
25189    cx.assert_editor_state(indoc! {"
25190        if [ \"$1\" = \"test\" ]; then
25191            echo \"foo bar\"
25192        elseˇ
25193    "});
25194
25195    // test `elif` auto outdents when typed inside `if` block
25196    cx.set_state(indoc! {"
25197        if [ \"$1\" = \"test\" ]; then
25198            echo \"foo bar\"
25199            ˇ
25200    "});
25201    cx.update_editor(|editor, window, cx| {
25202        editor.handle_input("elif", window, cx);
25203    });
25204    cx.assert_editor_state(indoc! {"
25205        if [ \"$1\" = \"test\" ]; then
25206            echo \"foo bar\"
25207        elifˇ
25208    "});
25209
25210    // test `fi` auto outdents when typed inside `else` block
25211    cx.set_state(indoc! {"
25212        if [ \"$1\" = \"test\" ]; then
25213            echo \"foo bar\"
25214        else
25215            echo \"bar baz\"
25216            ˇ
25217    "});
25218    cx.update_editor(|editor, window, cx| {
25219        editor.handle_input("fi", window, cx);
25220    });
25221    cx.assert_editor_state(indoc! {"
25222        if [ \"$1\" = \"test\" ]; then
25223            echo \"foo bar\"
25224        else
25225            echo \"bar baz\"
25226        fiˇ
25227    "});
25228
25229    // test `done` auto outdents when typed inside `while` block
25230    cx.set_state(indoc! {"
25231        while read line; do
25232            echo \"$line\"
25233            ˇ
25234    "});
25235    cx.update_editor(|editor, window, cx| {
25236        editor.handle_input("done", window, cx);
25237    });
25238    cx.assert_editor_state(indoc! {"
25239        while read line; do
25240            echo \"$line\"
25241        doneˇ
25242    "});
25243
25244    // test `done` auto outdents when typed inside `for` block
25245    cx.set_state(indoc! {"
25246        for file in *.txt; do
25247            cat \"$file\"
25248            ˇ
25249    "});
25250    cx.update_editor(|editor, window, cx| {
25251        editor.handle_input("done", window, cx);
25252    });
25253    cx.assert_editor_state(indoc! {"
25254        for file in *.txt; do
25255            cat \"$file\"
25256        doneˇ
25257    "});
25258
25259    // test `esac` auto outdents when typed inside `case` block
25260    cx.set_state(indoc! {"
25261        case \"$1\" in
25262            start)
25263                echo \"foo bar\"
25264                ;;
25265            stop)
25266                echo \"bar baz\"
25267                ;;
25268            ˇ
25269    "});
25270    cx.update_editor(|editor, window, cx| {
25271        editor.handle_input("esac", window, cx);
25272    });
25273    cx.assert_editor_state(indoc! {"
25274        case \"$1\" in
25275            start)
25276                echo \"foo bar\"
25277                ;;
25278            stop)
25279                echo \"bar baz\"
25280                ;;
25281        esacˇ
25282    "});
25283
25284    // test `*)` auto outdents when typed inside `case` block
25285    cx.set_state(indoc! {"
25286        case \"$1\" in
25287            start)
25288                echo \"foo bar\"
25289                ;;
25290                ˇ
25291    "});
25292    cx.update_editor(|editor, window, cx| {
25293        editor.handle_input("*)", window, cx);
25294    });
25295    cx.assert_editor_state(indoc! {"
25296        case \"$1\" in
25297            start)
25298                echo \"foo bar\"
25299                ;;
25300            *)ˇ
25301    "});
25302
25303    // test `fi` outdents to correct level with nested if blocks
25304    cx.set_state(indoc! {"
25305        if [ \"$1\" = \"test\" ]; then
25306            echo \"outer if\"
25307            if [ \"$2\" = \"debug\" ]; then
25308                echo \"inner if\"
25309                ˇ
25310    "});
25311    cx.update_editor(|editor, window, cx| {
25312        editor.handle_input("fi", window, cx);
25313    });
25314    cx.assert_editor_state(indoc! {"
25315        if [ \"$1\" = \"test\" ]; then
25316            echo \"outer if\"
25317            if [ \"$2\" = \"debug\" ]; then
25318                echo \"inner if\"
25319            fiˇ
25320    "});
25321}
25322
25323#[gpui::test]
25324async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
25325    init_test(cx, |_| {});
25326    update_test_language_settings(cx, |settings| {
25327        settings.defaults.extend_comment_on_newline = Some(false);
25328    });
25329    let mut cx = EditorTestContext::new(cx).await;
25330    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25331    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25332
25333    // test correct indent after newline on comment
25334    cx.set_state(indoc! {"
25335        # COMMENT:ˇ
25336    "});
25337    cx.update_editor(|editor, window, cx| {
25338        editor.newline(&Newline, window, cx);
25339    });
25340    cx.assert_editor_state(indoc! {"
25341        # COMMENT:
25342        ˇ
25343    "});
25344
25345    // test correct indent after newline after `then`
25346    cx.set_state(indoc! {"
25347
25348        if [ \"$1\" = \"test\" ]; thenˇ
25349    "});
25350    cx.update_editor(|editor, window, cx| {
25351        editor.newline(&Newline, window, cx);
25352    });
25353    cx.run_until_parked();
25354    cx.assert_editor_state(indoc! {"
25355
25356        if [ \"$1\" = \"test\" ]; then
25357            ˇ
25358    "});
25359
25360    // test correct indent after newline after `else`
25361    cx.set_state(indoc! {"
25362        if [ \"$1\" = \"test\" ]; then
25363        elseˇ
25364    "});
25365    cx.update_editor(|editor, window, cx| {
25366        editor.newline(&Newline, window, cx);
25367    });
25368    cx.run_until_parked();
25369    cx.assert_editor_state(indoc! {"
25370        if [ \"$1\" = \"test\" ]; then
25371        else
25372            ˇ
25373    "});
25374
25375    // test correct indent after newline after `elif`
25376    cx.set_state(indoc! {"
25377        if [ \"$1\" = \"test\" ]; then
25378        elifˇ
25379    "});
25380    cx.update_editor(|editor, window, cx| {
25381        editor.newline(&Newline, window, cx);
25382    });
25383    cx.run_until_parked();
25384    cx.assert_editor_state(indoc! {"
25385        if [ \"$1\" = \"test\" ]; then
25386        elif
25387            ˇ
25388    "});
25389
25390    // test correct indent after newline after `do`
25391    cx.set_state(indoc! {"
25392        for file in *.txt; doˇ
25393    "});
25394    cx.update_editor(|editor, window, cx| {
25395        editor.newline(&Newline, window, cx);
25396    });
25397    cx.run_until_parked();
25398    cx.assert_editor_state(indoc! {"
25399        for file in *.txt; do
25400            ˇ
25401    "});
25402
25403    // test correct indent after newline after case pattern
25404    cx.set_state(indoc! {"
25405        case \"$1\" in
25406            start)ˇ
25407    "});
25408    cx.update_editor(|editor, window, cx| {
25409        editor.newline(&Newline, window, cx);
25410    });
25411    cx.run_until_parked();
25412    cx.assert_editor_state(indoc! {"
25413        case \"$1\" in
25414            start)
25415                ˇ
25416    "});
25417
25418    // test correct indent after newline after case pattern
25419    cx.set_state(indoc! {"
25420        case \"$1\" in
25421            start)
25422                ;;
25423            *)ˇ
25424    "});
25425    cx.update_editor(|editor, window, cx| {
25426        editor.newline(&Newline, window, cx);
25427    });
25428    cx.run_until_parked();
25429    cx.assert_editor_state(indoc! {"
25430        case \"$1\" in
25431            start)
25432                ;;
25433            *)
25434                ˇ
25435    "});
25436
25437    // test correct indent after newline after function opening brace
25438    cx.set_state(indoc! {"
25439        function test() {ˇ}
25440    "});
25441    cx.update_editor(|editor, window, cx| {
25442        editor.newline(&Newline, window, cx);
25443    });
25444    cx.run_until_parked();
25445    cx.assert_editor_state(indoc! {"
25446        function test() {
25447            ˇ
25448        }
25449    "});
25450
25451    // test no extra indent after semicolon on same line
25452    cx.set_state(indoc! {"
25453        echo \"test\"25454    "});
25455    cx.update_editor(|editor, window, cx| {
25456        editor.newline(&Newline, window, cx);
25457    });
25458    cx.run_until_parked();
25459    cx.assert_editor_state(indoc! {"
25460        echo \"test\";
25461        ˇ
25462    "});
25463}
25464
25465fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
25466    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
25467    point..point
25468}
25469
25470#[track_caller]
25471fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
25472    let (text, ranges) = marked_text_ranges(marked_text, true);
25473    assert_eq!(editor.text(cx), text);
25474    assert_eq!(
25475        editor.selections.ranges(&editor.display_snapshot(cx)),
25476        ranges,
25477        "Assert selections are {}",
25478        marked_text
25479    );
25480}
25481
25482pub fn handle_signature_help_request(
25483    cx: &mut EditorLspTestContext,
25484    mocked_response: lsp::SignatureHelp,
25485) -> impl Future<Output = ()> + use<> {
25486    let mut request =
25487        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
25488            let mocked_response = mocked_response.clone();
25489            async move { Ok(Some(mocked_response)) }
25490        });
25491
25492    async move {
25493        request.next().await;
25494    }
25495}
25496
25497#[track_caller]
25498pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
25499    cx.update_editor(|editor, _, _| {
25500        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
25501            let entries = menu.entries.borrow();
25502            let entries = entries
25503                .iter()
25504                .map(|entry| entry.string.as_str())
25505                .collect::<Vec<_>>();
25506            assert_eq!(entries, expected);
25507        } else {
25508            panic!("Expected completions menu");
25509        }
25510    });
25511}
25512
25513/// Handle completion request passing a marked string specifying where the completion
25514/// should be triggered from using '|' character, what range should be replaced, and what completions
25515/// should be returned using '<' and '>' to delimit the range.
25516///
25517/// Also see `handle_completion_request_with_insert_and_replace`.
25518#[track_caller]
25519pub fn handle_completion_request(
25520    marked_string: &str,
25521    completions: Vec<&'static str>,
25522    is_incomplete: bool,
25523    counter: Arc<AtomicUsize>,
25524    cx: &mut EditorLspTestContext,
25525) -> impl Future<Output = ()> {
25526    let complete_from_marker: TextRangeMarker = '|'.into();
25527    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25528    let (_, mut marked_ranges) = marked_text_ranges_by(
25529        marked_string,
25530        vec![complete_from_marker.clone(), replace_range_marker.clone()],
25531    );
25532
25533    let complete_from_position =
25534        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25535    let replace_range =
25536        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25537
25538    let mut request =
25539        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25540            let completions = completions.clone();
25541            counter.fetch_add(1, atomic::Ordering::Release);
25542            async move {
25543                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25544                assert_eq!(
25545                    params.text_document_position.position,
25546                    complete_from_position
25547                );
25548                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
25549                    is_incomplete,
25550                    item_defaults: None,
25551                    items: completions
25552                        .iter()
25553                        .map(|completion_text| lsp::CompletionItem {
25554                            label: completion_text.to_string(),
25555                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
25556                                range: replace_range,
25557                                new_text: completion_text.to_string(),
25558                            })),
25559                            ..Default::default()
25560                        })
25561                        .collect(),
25562                })))
25563            }
25564        });
25565
25566    async move {
25567        request.next().await;
25568    }
25569}
25570
25571/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
25572/// given instead, which also contains an `insert` range.
25573///
25574/// This function uses markers to define ranges:
25575/// - `|` marks the cursor position
25576/// - `<>` marks the replace range
25577/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
25578pub fn handle_completion_request_with_insert_and_replace(
25579    cx: &mut EditorLspTestContext,
25580    marked_string: &str,
25581    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
25582    counter: Arc<AtomicUsize>,
25583) -> impl Future<Output = ()> {
25584    let complete_from_marker: TextRangeMarker = '|'.into();
25585    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25586    let insert_range_marker: TextRangeMarker = ('{', '}').into();
25587
25588    let (_, mut marked_ranges) = marked_text_ranges_by(
25589        marked_string,
25590        vec![
25591            complete_from_marker.clone(),
25592            replace_range_marker.clone(),
25593            insert_range_marker.clone(),
25594        ],
25595    );
25596
25597    let complete_from_position =
25598        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25599    let replace_range =
25600        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25601
25602    let insert_range = match marked_ranges.remove(&insert_range_marker) {
25603        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
25604        _ => lsp::Range {
25605            start: replace_range.start,
25606            end: complete_from_position,
25607        },
25608    };
25609
25610    let mut request =
25611        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25612            let completions = completions.clone();
25613            counter.fetch_add(1, atomic::Ordering::Release);
25614            async move {
25615                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25616                assert_eq!(
25617                    params.text_document_position.position, complete_from_position,
25618                    "marker `|` position doesn't match",
25619                );
25620                Ok(Some(lsp::CompletionResponse::Array(
25621                    completions
25622                        .iter()
25623                        .map(|(label, new_text)| lsp::CompletionItem {
25624                            label: label.to_string(),
25625                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
25626                                lsp::InsertReplaceEdit {
25627                                    insert: insert_range,
25628                                    replace: replace_range,
25629                                    new_text: new_text.to_string(),
25630                                },
25631                            )),
25632                            ..Default::default()
25633                        })
25634                        .collect(),
25635                )))
25636            }
25637        });
25638
25639    async move {
25640        request.next().await;
25641    }
25642}
25643
25644fn handle_resolve_completion_request(
25645    cx: &mut EditorLspTestContext,
25646    edits: Option<Vec<(&'static str, &'static str)>>,
25647) -> impl Future<Output = ()> {
25648    let edits = edits.map(|edits| {
25649        edits
25650            .iter()
25651            .map(|(marked_string, new_text)| {
25652                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
25653                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
25654                lsp::TextEdit::new(replace_range, new_text.to_string())
25655            })
25656            .collect::<Vec<_>>()
25657    });
25658
25659    let mut request =
25660        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
25661            let edits = edits.clone();
25662            async move {
25663                Ok(lsp::CompletionItem {
25664                    additional_text_edits: edits,
25665                    ..Default::default()
25666                })
25667            }
25668        });
25669
25670    async move {
25671        request.next().await;
25672    }
25673}
25674
25675pub(crate) fn update_test_language_settings(
25676    cx: &mut TestAppContext,
25677    f: impl Fn(&mut AllLanguageSettingsContent),
25678) {
25679    cx.update(|cx| {
25680        SettingsStore::update_global(cx, |store, cx| {
25681            store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
25682        });
25683    });
25684}
25685
25686pub(crate) fn update_test_project_settings(
25687    cx: &mut TestAppContext,
25688    f: impl Fn(&mut ProjectSettingsContent),
25689) {
25690    cx.update(|cx| {
25691        SettingsStore::update_global(cx, |store, cx| {
25692            store.update_user_settings(cx, |settings| f(&mut settings.project));
25693        });
25694    });
25695}
25696
25697pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
25698    cx.update(|cx| {
25699        assets::Assets.load_test_fonts(cx);
25700        let store = SettingsStore::test(cx);
25701        cx.set_global(store);
25702        theme::init(theme::LoadThemes::JustBase, cx);
25703        release_channel::init(SemanticVersion::default(), cx);
25704        crate::init(cx);
25705    });
25706    zlog::init_test();
25707    update_test_language_settings(cx, f);
25708}
25709
25710#[track_caller]
25711fn assert_hunk_revert(
25712    not_reverted_text_with_selections: &str,
25713    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
25714    expected_reverted_text_with_selections: &str,
25715    base_text: &str,
25716    cx: &mut EditorLspTestContext,
25717) {
25718    cx.set_state(not_reverted_text_with_selections);
25719    cx.set_head_text(base_text);
25720    cx.executor().run_until_parked();
25721
25722    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
25723        let snapshot = editor.snapshot(window, cx);
25724        let reverted_hunk_statuses = snapshot
25725            .buffer_snapshot()
25726            .diff_hunks_in_range(0..snapshot.buffer_snapshot().len())
25727            .map(|hunk| hunk.status().kind)
25728            .collect::<Vec<_>>();
25729
25730        editor.git_restore(&Default::default(), window, cx);
25731        reverted_hunk_statuses
25732    });
25733    cx.executor().run_until_parked();
25734    cx.assert_editor_state(expected_reverted_text_with_selections);
25735    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
25736}
25737
25738#[gpui::test(iterations = 10)]
25739async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
25740    init_test(cx, |_| {});
25741
25742    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
25743    let counter = diagnostic_requests.clone();
25744
25745    let fs = FakeFs::new(cx.executor());
25746    fs.insert_tree(
25747        path!("/a"),
25748        json!({
25749            "first.rs": "fn main() { let a = 5; }",
25750            "second.rs": "// Test file",
25751        }),
25752    )
25753    .await;
25754
25755    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25756    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25757    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25758
25759    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25760    language_registry.add(rust_lang());
25761    let mut fake_servers = language_registry.register_fake_lsp(
25762        "Rust",
25763        FakeLspAdapter {
25764            capabilities: lsp::ServerCapabilities {
25765                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
25766                    lsp::DiagnosticOptions {
25767                        identifier: None,
25768                        inter_file_dependencies: true,
25769                        workspace_diagnostics: true,
25770                        work_done_progress_options: Default::default(),
25771                    },
25772                )),
25773                ..Default::default()
25774            },
25775            ..Default::default()
25776        },
25777    );
25778
25779    let editor = workspace
25780        .update(cx, |workspace, window, cx| {
25781            workspace.open_abs_path(
25782                PathBuf::from(path!("/a/first.rs")),
25783                OpenOptions::default(),
25784                window,
25785                cx,
25786            )
25787        })
25788        .unwrap()
25789        .await
25790        .unwrap()
25791        .downcast::<Editor>()
25792        .unwrap();
25793    let fake_server = fake_servers.next().await.unwrap();
25794    let server_id = fake_server.server.server_id();
25795    let mut first_request = fake_server
25796        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
25797            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
25798            let result_id = Some(new_result_id.to_string());
25799            assert_eq!(
25800                params.text_document.uri,
25801                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25802            );
25803            async move {
25804                Ok(lsp::DocumentDiagnosticReportResult::Report(
25805                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
25806                        related_documents: None,
25807                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
25808                            items: Vec::new(),
25809                            result_id,
25810                        },
25811                    }),
25812                ))
25813            }
25814        });
25815
25816    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
25817        project.update(cx, |project, cx| {
25818            let buffer_id = editor
25819                .read(cx)
25820                .buffer()
25821                .read(cx)
25822                .as_singleton()
25823                .expect("created a singleton buffer")
25824                .read(cx)
25825                .remote_id();
25826            let buffer_result_id = project
25827                .lsp_store()
25828                .read(cx)
25829                .result_id(server_id, buffer_id, cx);
25830            assert_eq!(expected, buffer_result_id);
25831        });
25832    };
25833
25834    ensure_result_id(None, cx);
25835    cx.executor().advance_clock(Duration::from_millis(60));
25836    cx.executor().run_until_parked();
25837    assert_eq!(
25838        diagnostic_requests.load(atomic::Ordering::Acquire),
25839        1,
25840        "Opening file should trigger diagnostic request"
25841    );
25842    first_request
25843        .next()
25844        .await
25845        .expect("should have sent the first diagnostics pull request");
25846    ensure_result_id(Some("1".to_string()), cx);
25847
25848    // Editing should trigger diagnostics
25849    editor.update_in(cx, |editor, window, cx| {
25850        editor.handle_input("2", window, cx)
25851    });
25852    cx.executor().advance_clock(Duration::from_millis(60));
25853    cx.executor().run_until_parked();
25854    assert_eq!(
25855        diagnostic_requests.load(atomic::Ordering::Acquire),
25856        2,
25857        "Editing should trigger diagnostic request"
25858    );
25859    ensure_result_id(Some("2".to_string()), cx);
25860
25861    // Moving cursor should not trigger diagnostic request
25862    editor.update_in(cx, |editor, window, cx| {
25863        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25864            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
25865        });
25866    });
25867    cx.executor().advance_clock(Duration::from_millis(60));
25868    cx.executor().run_until_parked();
25869    assert_eq!(
25870        diagnostic_requests.load(atomic::Ordering::Acquire),
25871        2,
25872        "Cursor movement should not trigger diagnostic request"
25873    );
25874    ensure_result_id(Some("2".to_string()), cx);
25875    // Multiple rapid edits should be debounced
25876    for _ in 0..5 {
25877        editor.update_in(cx, |editor, window, cx| {
25878            editor.handle_input("x", window, cx)
25879        });
25880    }
25881    cx.executor().advance_clock(Duration::from_millis(60));
25882    cx.executor().run_until_parked();
25883
25884    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
25885    assert!(
25886        final_requests <= 4,
25887        "Multiple rapid edits should be debounced (got {final_requests} requests)",
25888    );
25889    ensure_result_id(Some(final_requests.to_string()), cx);
25890}
25891
25892#[gpui::test]
25893async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
25894    // Regression test for issue #11671
25895    // Previously, adding a cursor after moving multiple cursors would reset
25896    // the cursor count instead of adding to the existing cursors.
25897    init_test(cx, |_| {});
25898    let mut cx = EditorTestContext::new(cx).await;
25899
25900    // Create a simple buffer with cursor at start
25901    cx.set_state(indoc! {"
25902        ˇaaaa
25903        bbbb
25904        cccc
25905        dddd
25906        eeee
25907        ffff
25908        gggg
25909        hhhh"});
25910
25911    // Add 2 cursors below (so we have 3 total)
25912    cx.update_editor(|editor, window, cx| {
25913        editor.add_selection_below(&Default::default(), window, cx);
25914        editor.add_selection_below(&Default::default(), window, cx);
25915    });
25916
25917    // Verify we have 3 cursors
25918    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
25919    assert_eq!(
25920        initial_count, 3,
25921        "Should have 3 cursors after adding 2 below"
25922    );
25923
25924    // Move down one line
25925    cx.update_editor(|editor, window, cx| {
25926        editor.move_down(&MoveDown, window, cx);
25927    });
25928
25929    // Add another cursor below
25930    cx.update_editor(|editor, window, cx| {
25931        editor.add_selection_below(&Default::default(), window, cx);
25932    });
25933
25934    // Should now have 4 cursors (3 original + 1 new)
25935    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
25936    assert_eq!(
25937        final_count, 4,
25938        "Should have 4 cursors after moving and adding another"
25939    );
25940}
25941
25942#[gpui::test]
25943async fn test_add_selection_skip_soft_wrap_option(cx: &mut TestAppContext) {
25944    init_test(cx, |_| {});
25945
25946    let mut cx = EditorTestContext::new(cx).await;
25947
25948    cx.set_state(indoc!(
25949        r#"ˇThis is a very long line that will be wrapped when soft wrapping is enabled
25950           Second line here"#
25951    ));
25952
25953    cx.update_editor(|editor, window, cx| {
25954        // Enable soft wrapping with a narrow width to force soft wrapping and
25955        // confirm that more than 2 rows are being displayed.
25956        editor.set_wrap_width(Some(100.0.into()), cx);
25957        assert!(editor.display_text(cx).lines().count() > 2);
25958
25959        editor.add_selection_below(
25960            &AddSelectionBelow {
25961                skip_soft_wrap: true,
25962            },
25963            window,
25964            cx,
25965        );
25966
25967        assert_eq!(
25968            display_ranges(editor, cx),
25969            &[
25970                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
25971                DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(8), 0),
25972            ]
25973        );
25974
25975        editor.add_selection_above(
25976            &AddSelectionAbove {
25977                skip_soft_wrap: true,
25978            },
25979            window,
25980            cx,
25981        );
25982
25983        assert_eq!(
25984            display_ranges(editor, cx),
25985            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
25986        );
25987
25988        editor.add_selection_below(
25989            &AddSelectionBelow {
25990                skip_soft_wrap: false,
25991            },
25992            window,
25993            cx,
25994        );
25995
25996        assert_eq!(
25997            display_ranges(editor, cx),
25998            &[
25999                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
26000                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
26001            ]
26002        );
26003
26004        editor.add_selection_above(
26005            &AddSelectionAbove {
26006                skip_soft_wrap: false,
26007            },
26008            window,
26009            cx,
26010        );
26011
26012        assert_eq!(
26013            display_ranges(editor, cx),
26014            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
26015        );
26016    });
26017}
26018
26019#[gpui::test(iterations = 10)]
26020async fn test_document_colors(cx: &mut TestAppContext) {
26021    let expected_color = Rgba {
26022        r: 0.33,
26023        g: 0.33,
26024        b: 0.33,
26025        a: 0.33,
26026    };
26027
26028    init_test(cx, |_| {});
26029
26030    let fs = FakeFs::new(cx.executor());
26031    fs.insert_tree(
26032        path!("/a"),
26033        json!({
26034            "first.rs": "fn main() { let a = 5; }",
26035        }),
26036    )
26037    .await;
26038
26039    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
26040    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
26041    let cx = &mut VisualTestContext::from_window(*workspace, cx);
26042
26043    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26044    language_registry.add(rust_lang());
26045    let mut fake_servers = language_registry.register_fake_lsp(
26046        "Rust",
26047        FakeLspAdapter {
26048            capabilities: lsp::ServerCapabilities {
26049                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
26050                ..lsp::ServerCapabilities::default()
26051            },
26052            name: "rust-analyzer",
26053            ..FakeLspAdapter::default()
26054        },
26055    );
26056    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
26057        "Rust",
26058        FakeLspAdapter {
26059            capabilities: lsp::ServerCapabilities {
26060                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
26061                ..lsp::ServerCapabilities::default()
26062            },
26063            name: "not-rust-analyzer",
26064            ..FakeLspAdapter::default()
26065        },
26066    );
26067
26068    let editor = workspace
26069        .update(cx, |workspace, window, cx| {
26070            workspace.open_abs_path(
26071                PathBuf::from(path!("/a/first.rs")),
26072                OpenOptions::default(),
26073                window,
26074                cx,
26075            )
26076        })
26077        .unwrap()
26078        .await
26079        .unwrap()
26080        .downcast::<Editor>()
26081        .unwrap();
26082    let fake_language_server = fake_servers.next().await.unwrap();
26083    let fake_language_server_without_capabilities =
26084        fake_servers_without_capabilities.next().await.unwrap();
26085    let requests_made = Arc::new(AtomicUsize::new(0));
26086    let closure_requests_made = Arc::clone(&requests_made);
26087    let mut color_request_handle = fake_language_server
26088        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
26089            let requests_made = Arc::clone(&closure_requests_made);
26090            async move {
26091                assert_eq!(
26092                    params.text_document.uri,
26093                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
26094                );
26095                requests_made.fetch_add(1, atomic::Ordering::Release);
26096                Ok(vec![
26097                    lsp::ColorInformation {
26098                        range: lsp::Range {
26099                            start: lsp::Position {
26100                                line: 0,
26101                                character: 0,
26102                            },
26103                            end: lsp::Position {
26104                                line: 0,
26105                                character: 1,
26106                            },
26107                        },
26108                        color: lsp::Color {
26109                            red: 0.33,
26110                            green: 0.33,
26111                            blue: 0.33,
26112                            alpha: 0.33,
26113                        },
26114                    },
26115                    lsp::ColorInformation {
26116                        range: lsp::Range {
26117                            start: lsp::Position {
26118                                line: 0,
26119                                character: 0,
26120                            },
26121                            end: lsp::Position {
26122                                line: 0,
26123                                character: 1,
26124                            },
26125                        },
26126                        color: lsp::Color {
26127                            red: 0.33,
26128                            green: 0.33,
26129                            blue: 0.33,
26130                            alpha: 0.33,
26131                        },
26132                    },
26133                ])
26134            }
26135        });
26136
26137    let _handle = fake_language_server_without_capabilities
26138        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
26139            panic!("Should not be called");
26140        });
26141    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26142    color_request_handle.next().await.unwrap();
26143    cx.run_until_parked();
26144    assert_eq!(
26145        1,
26146        requests_made.load(atomic::Ordering::Acquire),
26147        "Should query for colors once per editor open"
26148    );
26149    editor.update_in(cx, |editor, _, cx| {
26150        assert_eq!(
26151            vec![expected_color],
26152            extract_color_inlays(editor, cx),
26153            "Should have an initial inlay"
26154        );
26155    });
26156
26157    // opening another file in a split should not influence the LSP query counter
26158    workspace
26159        .update(cx, |workspace, window, cx| {
26160            assert_eq!(
26161                workspace.panes().len(),
26162                1,
26163                "Should have one pane with one editor"
26164            );
26165            workspace.move_item_to_pane_in_direction(
26166                &MoveItemToPaneInDirection {
26167                    direction: SplitDirection::Right,
26168                    focus: false,
26169                    clone: true,
26170                },
26171                window,
26172                cx,
26173            );
26174        })
26175        .unwrap();
26176    cx.run_until_parked();
26177    workspace
26178        .update(cx, |workspace, _, cx| {
26179            let panes = workspace.panes();
26180            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
26181            for pane in panes {
26182                let editor = pane
26183                    .read(cx)
26184                    .active_item()
26185                    .and_then(|item| item.downcast::<Editor>())
26186                    .expect("Should have opened an editor in each split");
26187                let editor_file = editor
26188                    .read(cx)
26189                    .buffer()
26190                    .read(cx)
26191                    .as_singleton()
26192                    .expect("test deals with singleton buffers")
26193                    .read(cx)
26194                    .file()
26195                    .expect("test buffese should have a file")
26196                    .path();
26197                assert_eq!(
26198                    editor_file.as_ref(),
26199                    rel_path("first.rs"),
26200                    "Both editors should be opened for the same file"
26201                )
26202            }
26203        })
26204        .unwrap();
26205
26206    cx.executor().advance_clock(Duration::from_millis(500));
26207    let save = editor.update_in(cx, |editor, window, cx| {
26208        editor.move_to_end(&MoveToEnd, window, cx);
26209        editor.handle_input("dirty", window, cx);
26210        editor.save(
26211            SaveOptions {
26212                format: true,
26213                autosave: true,
26214            },
26215            project.clone(),
26216            window,
26217            cx,
26218        )
26219    });
26220    save.await.unwrap();
26221
26222    color_request_handle.next().await.unwrap();
26223    cx.run_until_parked();
26224    assert_eq!(
26225        2,
26226        requests_made.load(atomic::Ordering::Acquire),
26227        "Should query for colors once per save (deduplicated) and once per formatting after save"
26228    );
26229
26230    drop(editor);
26231    let close = workspace
26232        .update(cx, |workspace, window, cx| {
26233            workspace.active_pane().update(cx, |pane, cx| {
26234                pane.close_active_item(&CloseActiveItem::default(), window, cx)
26235            })
26236        })
26237        .unwrap();
26238    close.await.unwrap();
26239    let close = workspace
26240        .update(cx, |workspace, window, cx| {
26241            workspace.active_pane().update(cx, |pane, cx| {
26242                pane.close_active_item(&CloseActiveItem::default(), window, cx)
26243            })
26244        })
26245        .unwrap();
26246    close.await.unwrap();
26247    assert_eq!(
26248        2,
26249        requests_made.load(atomic::Ordering::Acquire),
26250        "After saving and closing all editors, no extra requests should be made"
26251    );
26252    workspace
26253        .update(cx, |workspace, _, cx| {
26254            assert!(
26255                workspace.active_item(cx).is_none(),
26256                "Should close all editors"
26257            )
26258        })
26259        .unwrap();
26260
26261    workspace
26262        .update(cx, |workspace, window, cx| {
26263            workspace.active_pane().update(cx, |pane, cx| {
26264                pane.navigate_backward(&workspace::GoBack, window, cx);
26265            })
26266        })
26267        .unwrap();
26268    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26269    cx.run_until_parked();
26270    let editor = workspace
26271        .update(cx, |workspace, _, cx| {
26272            workspace
26273                .active_item(cx)
26274                .expect("Should have reopened the editor again after navigating back")
26275                .downcast::<Editor>()
26276                .expect("Should be an editor")
26277        })
26278        .unwrap();
26279
26280    assert_eq!(
26281        2,
26282        requests_made.load(atomic::Ordering::Acquire),
26283        "Cache should be reused on buffer close and reopen"
26284    );
26285    editor.update(cx, |editor, cx| {
26286        assert_eq!(
26287            vec![expected_color],
26288            extract_color_inlays(editor, cx),
26289            "Should have an initial inlay"
26290        );
26291    });
26292
26293    drop(color_request_handle);
26294    let closure_requests_made = Arc::clone(&requests_made);
26295    let mut empty_color_request_handle = fake_language_server
26296        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
26297            let requests_made = Arc::clone(&closure_requests_made);
26298            async move {
26299                assert_eq!(
26300                    params.text_document.uri,
26301                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
26302                );
26303                requests_made.fetch_add(1, atomic::Ordering::Release);
26304                Ok(Vec::new())
26305            }
26306        });
26307    let save = editor.update_in(cx, |editor, window, cx| {
26308        editor.move_to_end(&MoveToEnd, window, cx);
26309        editor.handle_input("dirty_again", window, cx);
26310        editor.save(
26311            SaveOptions {
26312                format: false,
26313                autosave: true,
26314            },
26315            project.clone(),
26316            window,
26317            cx,
26318        )
26319    });
26320    save.await.unwrap();
26321
26322    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26323    empty_color_request_handle.next().await.unwrap();
26324    cx.run_until_parked();
26325    assert_eq!(
26326        3,
26327        requests_made.load(atomic::Ordering::Acquire),
26328        "Should query for colors once per save only, as formatting was not requested"
26329    );
26330    editor.update(cx, |editor, cx| {
26331        assert_eq!(
26332            Vec::<Rgba>::new(),
26333            extract_color_inlays(editor, cx),
26334            "Should clear all colors when the server returns an empty response"
26335        );
26336    });
26337}
26338
26339#[gpui::test]
26340async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
26341    init_test(cx, |_| {});
26342    let (editor, cx) = cx.add_window_view(Editor::single_line);
26343    editor.update_in(cx, |editor, window, cx| {
26344        editor.set_text("oops\n\nwow\n", window, cx)
26345    });
26346    cx.run_until_parked();
26347    editor.update(cx, |editor, cx| {
26348        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
26349    });
26350    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
26351    cx.run_until_parked();
26352    editor.update(cx, |editor, cx| {
26353        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
26354    });
26355}
26356
26357#[gpui::test]
26358async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
26359    init_test(cx, |_| {});
26360
26361    cx.update(|cx| {
26362        register_project_item::<Editor>(cx);
26363    });
26364
26365    let fs = FakeFs::new(cx.executor());
26366    fs.insert_tree("/root1", json!({})).await;
26367    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
26368        .await;
26369
26370    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
26371    let (workspace, cx) =
26372        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
26373
26374    let worktree_id = project.update(cx, |project, cx| {
26375        project.worktrees(cx).next().unwrap().read(cx).id()
26376    });
26377
26378    let handle = workspace
26379        .update_in(cx, |workspace, window, cx| {
26380            let project_path = (worktree_id, rel_path("one.pdf"));
26381            workspace.open_path(project_path, None, true, window, cx)
26382        })
26383        .await
26384        .unwrap();
26385
26386    assert_eq!(
26387        handle.to_any().entity_type(),
26388        TypeId::of::<InvalidItemView>()
26389    );
26390}
26391
26392#[gpui::test]
26393async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
26394    init_test(cx, |_| {});
26395
26396    let language = Arc::new(Language::new(
26397        LanguageConfig::default(),
26398        Some(tree_sitter_rust::LANGUAGE.into()),
26399    ));
26400
26401    // Test hierarchical sibling navigation
26402    let text = r#"
26403        fn outer() {
26404            if condition {
26405                let a = 1;
26406            }
26407            let b = 2;
26408        }
26409
26410        fn another() {
26411            let c = 3;
26412        }
26413    "#;
26414
26415    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
26416    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
26417    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
26418
26419    // Wait for parsing to complete
26420    editor
26421        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
26422        .await;
26423
26424    editor.update_in(cx, |editor, window, cx| {
26425        // Start by selecting "let a = 1;" inside the if block
26426        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26427            s.select_display_ranges([
26428                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
26429            ]);
26430        });
26431
26432        let initial_selection = editor
26433            .selections
26434            .display_ranges(&editor.display_snapshot(cx));
26435        assert_eq!(initial_selection.len(), 1, "Should have one selection");
26436
26437        // Test select next sibling - should move up levels to find the next sibling
26438        // Since "let a = 1;" has no siblings in the if block, it should move up
26439        // to find "let b = 2;" which is a sibling of the if block
26440        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
26441        let next_selection = editor
26442            .selections
26443            .display_ranges(&editor.display_snapshot(cx));
26444
26445        // Should have a selection and it should be different from the initial
26446        assert_eq!(
26447            next_selection.len(),
26448            1,
26449            "Should have one selection after next"
26450        );
26451        assert_ne!(
26452            next_selection[0], initial_selection[0],
26453            "Next sibling selection should be different"
26454        );
26455
26456        // Test hierarchical navigation by going to the end of the current function
26457        // and trying to navigate to the next function
26458        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26459            s.select_display_ranges([
26460                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
26461            ]);
26462        });
26463
26464        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
26465        let function_next_selection = editor
26466            .selections
26467            .display_ranges(&editor.display_snapshot(cx));
26468
26469        // Should move to the next function
26470        assert_eq!(
26471            function_next_selection.len(),
26472            1,
26473            "Should have one selection after function next"
26474        );
26475
26476        // Test select previous sibling navigation
26477        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
26478        let prev_selection = editor
26479            .selections
26480            .display_ranges(&editor.display_snapshot(cx));
26481
26482        // Should have a selection and it should be different
26483        assert_eq!(
26484            prev_selection.len(),
26485            1,
26486            "Should have one selection after prev"
26487        );
26488        assert_ne!(
26489            prev_selection[0], function_next_selection[0],
26490            "Previous sibling selection should be different from next"
26491        );
26492    });
26493}
26494
26495#[gpui::test]
26496async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
26497    init_test(cx, |_| {});
26498
26499    let mut cx = EditorTestContext::new(cx).await;
26500    cx.set_state(
26501        "let ˇvariable = 42;
26502let another = variable + 1;
26503let result = variable * 2;",
26504    );
26505
26506    // Set up document highlights manually (simulating LSP response)
26507    cx.update_editor(|editor, _window, cx| {
26508        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
26509
26510        // Create highlights for "variable" occurrences
26511        let highlight_ranges = [
26512            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
26513            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
26514            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
26515        ];
26516
26517        let anchor_ranges: Vec<_> = highlight_ranges
26518            .iter()
26519            .map(|range| range.clone().to_anchors(&buffer_snapshot))
26520            .collect();
26521
26522        editor.highlight_background::<DocumentHighlightRead>(
26523            &anchor_ranges,
26524            |theme| theme.colors().editor_document_highlight_read_background,
26525            cx,
26526        );
26527    });
26528
26529    // Go to next highlight - should move to second "variable"
26530    cx.update_editor(|editor, window, cx| {
26531        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26532    });
26533    cx.assert_editor_state(
26534        "let variable = 42;
26535let another = ˇvariable + 1;
26536let result = variable * 2;",
26537    );
26538
26539    // Go to next highlight - should move to third "variable"
26540    cx.update_editor(|editor, window, cx| {
26541        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26542    });
26543    cx.assert_editor_state(
26544        "let variable = 42;
26545let another = variable + 1;
26546let result = ˇvariable * 2;",
26547    );
26548
26549    // Go to next highlight - should stay at third "variable" (no wrap-around)
26550    cx.update_editor(|editor, window, cx| {
26551        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26552    });
26553    cx.assert_editor_state(
26554        "let variable = 42;
26555let another = variable + 1;
26556let result = ˇvariable * 2;",
26557    );
26558
26559    // Now test going backwards from third position
26560    cx.update_editor(|editor, window, cx| {
26561        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26562    });
26563    cx.assert_editor_state(
26564        "let variable = 42;
26565let another = ˇvariable + 1;
26566let result = variable * 2;",
26567    );
26568
26569    // Go to previous highlight - should move to first "variable"
26570    cx.update_editor(|editor, window, cx| {
26571        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26572    });
26573    cx.assert_editor_state(
26574        "let ˇvariable = 42;
26575let another = variable + 1;
26576let result = variable * 2;",
26577    );
26578
26579    // Go to previous highlight - should stay on first "variable"
26580    cx.update_editor(|editor, window, cx| {
26581        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26582    });
26583    cx.assert_editor_state(
26584        "let ˇvariable = 42;
26585let another = variable + 1;
26586let result = variable * 2;",
26587    );
26588}
26589
26590#[gpui::test]
26591async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
26592    cx: &mut gpui::TestAppContext,
26593) {
26594    init_test(cx, |_| {});
26595
26596    let url = "https://zed.dev";
26597
26598    let markdown_language = Arc::new(Language::new(
26599        LanguageConfig {
26600            name: "Markdown".into(),
26601            ..LanguageConfig::default()
26602        },
26603        None,
26604    ));
26605
26606    let mut cx = EditorTestContext::new(cx).await;
26607    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26608    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
26609
26610    cx.update_editor(|editor, window, cx| {
26611        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26612        editor.paste(&Paste, window, cx);
26613    });
26614
26615    cx.assert_editor_state(&format!(
26616        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)"
26617    ));
26618}
26619
26620#[gpui::test]
26621async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
26622    cx: &mut gpui::TestAppContext,
26623) {
26624    init_test(cx, |_| {});
26625
26626    let url = "https://zed.dev";
26627
26628    let markdown_language = Arc::new(Language::new(
26629        LanguageConfig {
26630            name: "Markdown".into(),
26631            ..LanguageConfig::default()
26632        },
26633        None,
26634    ));
26635
26636    let mut cx = EditorTestContext::new(cx).await;
26637    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26638    cx.set_state(&format!(
26639        "Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»"
26640    ));
26641
26642    cx.update_editor(|editor, window, cx| {
26643        editor.copy(&Copy, window, cx);
26644    });
26645
26646    cx.set_state(&format!(
26647        "Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}"
26648    ));
26649
26650    cx.update_editor(|editor, window, cx| {
26651        editor.paste(&Paste, window, cx);
26652    });
26653
26654    cx.assert_editor_state(&format!(
26655        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}"
26656    ));
26657}
26658
26659#[gpui::test]
26660async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link(
26661    cx: &mut gpui::TestAppContext,
26662) {
26663    init_test(cx, |_| {});
26664
26665    let url = "https://zed.dev";
26666
26667    let markdown_language = Arc::new(Language::new(
26668        LanguageConfig {
26669            name: "Markdown".into(),
26670            ..LanguageConfig::default()
26671        },
26672        None,
26673    ));
26674
26675    let mut cx = EditorTestContext::new(cx).await;
26676    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26677    cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»");
26678
26679    cx.update_editor(|editor, window, cx| {
26680        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26681        editor.paste(&Paste, window, cx);
26682    });
26683
26684    cx.assert_editor_state(&format!("Please visit zed's homepage: {url}ˇ"));
26685}
26686
26687#[gpui::test]
26688async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link(
26689    cx: &mut gpui::TestAppContext,
26690) {
26691    init_test(cx, |_| {});
26692
26693    let text = "Awesome";
26694
26695    let markdown_language = Arc::new(Language::new(
26696        LanguageConfig {
26697            name: "Markdown".into(),
26698            ..LanguageConfig::default()
26699        },
26700        None,
26701    ));
26702
26703    let mut cx = EditorTestContext::new(cx).await;
26704    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26705    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»");
26706
26707    cx.update_editor(|editor, window, cx| {
26708        cx.write_to_clipboard(ClipboardItem::new_string(text.to_string()));
26709        editor.paste(&Paste, window, cx);
26710    });
26711
26712    cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ"));
26713}
26714
26715#[gpui::test]
26716async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language(
26717    cx: &mut gpui::TestAppContext,
26718) {
26719    init_test(cx, |_| {});
26720
26721    let url = "https://zed.dev";
26722
26723    let markdown_language = Arc::new(Language::new(
26724        LanguageConfig {
26725            name: "Rust".into(),
26726            ..LanguageConfig::default()
26727        },
26728        None,
26729    ));
26730
26731    let mut cx = EditorTestContext::new(cx).await;
26732    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26733    cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
26734
26735    cx.update_editor(|editor, window, cx| {
26736        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26737        editor.paste(&Paste, window, cx);
26738    });
26739
26740    cx.assert_editor_state(&format!(
26741        "// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)"
26742    ));
26743}
26744
26745#[gpui::test]
26746async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer(
26747    cx: &mut TestAppContext,
26748) {
26749    init_test(cx, |_| {});
26750
26751    let url = "https://zed.dev";
26752
26753    let markdown_language = Arc::new(Language::new(
26754        LanguageConfig {
26755            name: "Markdown".into(),
26756            ..LanguageConfig::default()
26757        },
26758        None,
26759    ));
26760
26761    let (editor, cx) = cx.add_window_view(|window, cx| {
26762        let multi_buffer = MultiBuffer::build_multi(
26763            [
26764                ("this will embed -> link", vec![Point::row_range(0..1)]),
26765                ("this will replace -> link", vec![Point::row_range(0..1)]),
26766            ],
26767            cx,
26768        );
26769        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
26770        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26771            s.select_ranges(vec![
26772                Point::new(0, 19)..Point::new(0, 23),
26773                Point::new(1, 21)..Point::new(1, 25),
26774            ])
26775        });
26776        let first_buffer_id = multi_buffer
26777            .read(cx)
26778            .excerpt_buffer_ids()
26779            .into_iter()
26780            .next()
26781            .unwrap();
26782        let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
26783        first_buffer.update(cx, |buffer, cx| {
26784            buffer.set_language(Some(markdown_language.clone()), cx);
26785        });
26786
26787        editor
26788    });
26789    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
26790
26791    cx.update_editor(|editor, window, cx| {
26792        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26793        editor.paste(&Paste, window, cx);
26794    });
26795
26796    cx.assert_editor_state(&format!(
26797        "this will embed -> [link]({url}\nthis will replace -> {url}ˇ"
26798    ));
26799}
26800
26801#[gpui::test]
26802async fn test_race_in_multibuffer_save(cx: &mut TestAppContext) {
26803    init_test(cx, |_| {});
26804
26805    let fs = FakeFs::new(cx.executor());
26806    fs.insert_tree(
26807        path!("/project"),
26808        json!({
26809            "first.rs": "# First Document\nSome content here.",
26810            "second.rs": "Plain text content for second file.",
26811        }),
26812    )
26813    .await;
26814
26815    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
26816    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
26817    let cx = &mut VisualTestContext::from_window(*workspace, cx);
26818
26819    let language = rust_lang();
26820    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26821    language_registry.add(language.clone());
26822    let mut fake_servers = language_registry.register_fake_lsp(
26823        "Rust",
26824        FakeLspAdapter {
26825            ..FakeLspAdapter::default()
26826        },
26827    );
26828
26829    let buffer1 = project
26830        .update(cx, |project, cx| {
26831            project.open_local_buffer(PathBuf::from(path!("/project/first.rs")), cx)
26832        })
26833        .await
26834        .unwrap();
26835    let buffer2 = project
26836        .update(cx, |project, cx| {
26837            project.open_local_buffer(PathBuf::from(path!("/project/second.rs")), cx)
26838        })
26839        .await
26840        .unwrap();
26841
26842    let multi_buffer = cx.new(|cx| {
26843        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
26844        multi_buffer.set_excerpts_for_path(
26845            PathKey::for_buffer(&buffer1, cx),
26846            buffer1.clone(),
26847            [Point::zero()..buffer1.read(cx).max_point()],
26848            3,
26849            cx,
26850        );
26851        multi_buffer.set_excerpts_for_path(
26852            PathKey::for_buffer(&buffer2, cx),
26853            buffer2.clone(),
26854            [Point::zero()..buffer1.read(cx).max_point()],
26855            3,
26856            cx,
26857        );
26858        multi_buffer
26859    });
26860
26861    let (editor, cx) = cx.add_window_view(|window, cx| {
26862        Editor::new(
26863            EditorMode::full(),
26864            multi_buffer,
26865            Some(project.clone()),
26866            window,
26867            cx,
26868        )
26869    });
26870
26871    let fake_language_server = fake_servers.next().await.unwrap();
26872
26873    buffer1.update(cx, |buffer, cx| buffer.edit([(0..0, "hello!")], None, cx));
26874
26875    let save = editor.update_in(cx, |editor, window, cx| {
26876        assert!(editor.is_dirty(cx));
26877
26878        editor.save(
26879            SaveOptions {
26880                format: true,
26881                autosave: true,
26882            },
26883            project,
26884            window,
26885            cx,
26886        )
26887    });
26888    let (start_edit_tx, start_edit_rx) = oneshot::channel();
26889    let (done_edit_tx, done_edit_rx) = oneshot::channel();
26890    let mut done_edit_rx = Some(done_edit_rx);
26891    let mut start_edit_tx = Some(start_edit_tx);
26892
26893    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| {
26894        start_edit_tx.take().unwrap().send(()).unwrap();
26895        let done_edit_rx = done_edit_rx.take().unwrap();
26896        async move {
26897            done_edit_rx.await.unwrap();
26898            Ok(None)
26899        }
26900    });
26901
26902    start_edit_rx.await.unwrap();
26903    buffer2
26904        .update(cx, |buffer, cx| buffer.edit([(0..0, "world!")], None, cx))
26905        .unwrap();
26906
26907    done_edit_tx.send(()).unwrap();
26908
26909    save.await.unwrap();
26910    cx.update(|_, cx| assert!(editor.is_dirty(cx)));
26911}
26912
26913#[track_caller]
26914fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
26915    editor
26916        .all_inlays(cx)
26917        .into_iter()
26918        .filter_map(|inlay| inlay.get_color())
26919        .map(Rgba::from)
26920        .collect()
26921}
26922
26923#[gpui::test]
26924fn test_duplicate_line_up_on_last_line_without_newline(cx: &mut TestAppContext) {
26925    init_test(cx, |_| {});
26926
26927    let editor = cx.add_window(|window, cx| {
26928        let buffer = MultiBuffer::build_simple("line1\nline2", cx);
26929        build_editor(buffer, window, cx)
26930    });
26931
26932    editor
26933        .update(cx, |editor, window, cx| {
26934            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26935                s.select_display_ranges([
26936                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
26937                ])
26938            });
26939
26940            editor.duplicate_line_up(&DuplicateLineUp, window, cx);
26941
26942            assert_eq!(
26943                editor.display_text(cx),
26944                "line1\nline2\nline2",
26945                "Duplicating last line upward should create duplicate above, not on same line"
26946            );
26947
26948            assert_eq!(
26949                editor
26950                    .selections
26951                    .display_ranges(&editor.display_snapshot(cx)),
26952                vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)],
26953                "Selection should move to the duplicated line"
26954            );
26955        })
26956        .unwrap();
26957}
26958
26959#[gpui::test]
26960async fn test_copy_line_without_trailing_newline(cx: &mut TestAppContext) {
26961    init_test(cx, |_| {});
26962
26963    let mut cx = EditorTestContext::new(cx).await;
26964
26965    cx.set_state("line1\nline2ˇ");
26966
26967    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
26968
26969    let clipboard_text = cx
26970        .read_from_clipboard()
26971        .and_then(|item| item.text().as_deref().map(str::to_string));
26972
26973    assert_eq!(
26974        clipboard_text,
26975        Some("line2\n".to_string()),
26976        "Copying a line without trailing newline should include a newline"
26977    );
26978
26979    cx.set_state("line1\nˇ");
26980
26981    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
26982
26983    cx.assert_editor_state("line1\nline2\nˇ");
26984}
26985
26986#[gpui::test]
26987async fn test_end_of_editor_context(cx: &mut TestAppContext) {
26988    init_test(cx, |_| {});
26989
26990    let mut cx = EditorTestContext::new(cx).await;
26991
26992    cx.set_state("line1\nline2ˇ");
26993    cx.update_editor(|e, window, cx| {
26994        e.set_mode(EditorMode::SingleLine);
26995        assert!(e.key_context(window, cx).contains("end_of_input"));
26996    });
26997    cx.set_state("ˇline1\nline2");
26998    cx.update_editor(|e, window, cx| {
26999        assert!(!e.key_context(window, cx).contains("end_of_input"));
27000    });
27001    cx.set_state("line1ˇ\nline2");
27002    cx.update_editor(|e, window, cx| {
27003        assert!(!e.key_context(window, cx).contains("end_of_input"));
27004    });
27005}
27006
27007#[gpui::test]
27008async fn test_sticky_scroll(cx: &mut TestAppContext) {
27009    init_test(cx, |_| {});
27010    let mut cx = EditorTestContext::new(cx).await;
27011
27012    let buffer = indoc! {"
27013            ˇfn foo() {
27014                let abc = 123;
27015            }
27016            struct Bar;
27017            impl Bar {
27018                fn new() -> Self {
27019                    Self
27020                }
27021            }
27022            fn baz() {
27023            }
27024        "};
27025    cx.set_state(&buffer);
27026
27027    cx.update_editor(|e, _, cx| {
27028        e.buffer()
27029            .read(cx)
27030            .as_singleton()
27031            .unwrap()
27032            .update(cx, |buffer, cx| {
27033                buffer.set_language(Some(rust_lang()), cx);
27034            })
27035    });
27036
27037    let mut sticky_headers = |offset: ScrollOffset| {
27038        cx.update_editor(|e, window, cx| {
27039            e.scroll(gpui::Point { x: 0., y: offset }, None, window, cx);
27040            EditorElement::sticky_headers(&e, &e.snapshot(window, cx), cx)
27041                .into_iter()
27042                .map(
27043                    |StickyHeader {
27044                         start_point,
27045                         offset,
27046                         ..
27047                     }| { (start_point, offset) },
27048                )
27049                .collect::<Vec<_>>()
27050        })
27051    };
27052
27053    let fn_foo = Point { row: 0, column: 0 };
27054    let impl_bar = Point { row: 4, column: 0 };
27055    let fn_new = Point { row: 5, column: 4 };
27056
27057    assert_eq!(sticky_headers(0.0), vec![]);
27058    assert_eq!(sticky_headers(0.5), vec![(fn_foo, 0.0)]);
27059    assert_eq!(sticky_headers(1.0), vec![(fn_foo, 0.0)]);
27060    assert_eq!(sticky_headers(1.5), vec![(fn_foo, -0.5)]);
27061    assert_eq!(sticky_headers(2.0), vec![]);
27062    assert_eq!(sticky_headers(2.5), vec![]);
27063    assert_eq!(sticky_headers(3.0), vec![]);
27064    assert_eq!(sticky_headers(3.5), vec![]);
27065    assert_eq!(sticky_headers(4.0), vec![]);
27066    assert_eq!(sticky_headers(4.5), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
27067    assert_eq!(sticky_headers(5.0), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
27068    assert_eq!(sticky_headers(5.5), vec![(impl_bar, 0.0), (fn_new, 0.5)]);
27069    assert_eq!(sticky_headers(6.0), vec![(impl_bar, 0.0)]);
27070    assert_eq!(sticky_headers(6.5), vec![(impl_bar, 0.0)]);
27071    assert_eq!(sticky_headers(7.0), vec![(impl_bar, 0.0)]);
27072    assert_eq!(sticky_headers(7.5), vec![(impl_bar, -0.5)]);
27073    assert_eq!(sticky_headers(8.0), vec![]);
27074    assert_eq!(sticky_headers(8.5), vec![]);
27075    assert_eq!(sticky_headers(9.0), vec![]);
27076    assert_eq!(sticky_headers(9.5), vec![]);
27077    assert_eq!(sticky_headers(10.0), vec![]);
27078}
27079
27080#[gpui::test]
27081async fn test_scroll_by_clicking_sticky_header(cx: &mut TestAppContext) {
27082    init_test(cx, |_| {});
27083    cx.update(|cx| {
27084        SettingsStore::update_global(cx, |store, cx| {
27085            store.update_user_settings(cx, |settings| {
27086                settings.editor.sticky_scroll = Some(settings::StickyScrollContent {
27087                    enabled: Some(true),
27088                })
27089            });
27090        });
27091    });
27092    let mut cx = EditorTestContext::new(cx).await;
27093
27094    let line_height = cx.editor(|editor, window, _cx| {
27095        editor
27096            .style()
27097            .unwrap()
27098            .text
27099            .line_height_in_pixels(window.rem_size())
27100    });
27101
27102    let buffer = indoc! {"
27103            ˇfn foo() {
27104                let abc = 123;
27105            }
27106            struct Bar;
27107            impl Bar {
27108                fn new() -> Self {
27109                    Self
27110                }
27111            }
27112            fn baz() {
27113            }
27114        "};
27115    cx.set_state(&buffer);
27116
27117    cx.update_editor(|e, _, cx| {
27118        e.buffer()
27119            .read(cx)
27120            .as_singleton()
27121            .unwrap()
27122            .update(cx, |buffer, cx| {
27123                buffer.set_language(Some(rust_lang()), cx);
27124            })
27125    });
27126
27127    let fn_foo = || empty_range(0, 0);
27128    let impl_bar = || empty_range(4, 0);
27129    let fn_new = || empty_range(5, 4);
27130
27131    let mut scroll_and_click = |scroll_offset: ScrollOffset, click_offset: ScrollOffset| {
27132        cx.update_editor(|e, window, cx| {
27133            e.scroll(
27134                gpui::Point {
27135                    x: 0.,
27136                    y: scroll_offset,
27137                },
27138                None,
27139                window,
27140                cx,
27141            );
27142        });
27143        cx.simulate_click(
27144            gpui::Point {
27145                x: px(0.),
27146                y: click_offset as f32 * line_height,
27147            },
27148            Modifiers::none(),
27149        );
27150        cx.update_editor(|e, _, cx| (e.scroll_position(cx), display_ranges(e, cx)))
27151    };
27152
27153    assert_eq!(
27154        scroll_and_click(
27155            4.5, // impl Bar is halfway off the screen
27156            0.0  // click top of screen
27157        ),
27158        // scrolled to impl Bar
27159        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
27160    );
27161
27162    assert_eq!(
27163        scroll_and_click(
27164            4.5,  // impl Bar is halfway off the screen
27165            0.25  // click middle of impl Bar
27166        ),
27167        // scrolled to impl Bar
27168        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
27169    );
27170
27171    assert_eq!(
27172        scroll_and_click(
27173            4.5, // impl Bar is halfway off the screen
27174            1.5  // click below impl Bar (e.g. fn new())
27175        ),
27176        // scrolled to fn new() - this is below the impl Bar header which has persisted
27177        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
27178    );
27179
27180    assert_eq!(
27181        scroll_and_click(
27182            5.5,  // fn new is halfway underneath impl Bar
27183            0.75  // click on the overlap of impl Bar and fn new()
27184        ),
27185        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
27186    );
27187
27188    assert_eq!(
27189        scroll_and_click(
27190            5.5,  // fn new is halfway underneath impl Bar
27191            1.25  // click on the visible part of fn new()
27192        ),
27193        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
27194    );
27195
27196    assert_eq!(
27197        scroll_and_click(
27198            1.5, // fn foo is halfway off the screen
27199            0.0  // click top of screen
27200        ),
27201        (gpui::Point { x: 0., y: 0. }, vec![fn_foo()])
27202    );
27203
27204    assert_eq!(
27205        scroll_and_click(
27206            1.5,  // fn foo is halfway off the screen
27207            0.75  // click visible part of let abc...
27208        )
27209        .0,
27210        // no change in scroll
27211        // we don't assert on the visible_range because if we clicked the gutter, our line is fully selected
27212        (gpui::Point { x: 0., y: 1.5 })
27213    );
27214}
27215
27216#[gpui::test]
27217async fn test_next_prev_reference(cx: &mut TestAppContext) {
27218    const CYCLE_POSITIONS: &[&'static str] = &[
27219        indoc! {"
27220            fn foo() {
27221                let ˇabc = 123;
27222                let x = abc + 1;
27223                let y = abc + 2;
27224                let z = abc + 2;
27225            }
27226        "},
27227        indoc! {"
27228            fn foo() {
27229                let abc = 123;
27230                let x = ˇabc + 1;
27231                let y = abc + 2;
27232                let z = abc + 2;
27233            }
27234        "},
27235        indoc! {"
27236            fn foo() {
27237                let abc = 123;
27238                let x = abc + 1;
27239                let y = ˇabc + 2;
27240                let z = abc + 2;
27241            }
27242        "},
27243        indoc! {"
27244            fn foo() {
27245                let abc = 123;
27246                let x = abc + 1;
27247                let y = abc + 2;
27248                let z = ˇabc + 2;
27249            }
27250        "},
27251    ];
27252
27253    init_test(cx, |_| {});
27254
27255    let mut cx = EditorLspTestContext::new_rust(
27256        lsp::ServerCapabilities {
27257            references_provider: Some(lsp::OneOf::Left(true)),
27258            ..Default::default()
27259        },
27260        cx,
27261    )
27262    .await;
27263
27264    // importantly, the cursor is in the middle
27265    cx.set_state(indoc! {"
27266        fn foo() {
27267            let aˇbc = 123;
27268            let x = abc + 1;
27269            let y = abc + 2;
27270            let z = abc + 2;
27271        }
27272    "});
27273
27274    let reference_ranges = [
27275        lsp::Position::new(1, 8),
27276        lsp::Position::new(2, 12),
27277        lsp::Position::new(3, 12),
27278        lsp::Position::new(4, 12),
27279    ]
27280    .map(|start| lsp::Range::new(start, lsp::Position::new(start.line, start.character + 3)));
27281
27282    cx.lsp
27283        .set_request_handler::<lsp::request::References, _, _>(move |params, _cx| async move {
27284            Ok(Some(
27285                reference_ranges
27286                    .map(|range| lsp::Location {
27287                        uri: params.text_document_position.text_document.uri.clone(),
27288                        range,
27289                    })
27290                    .to_vec(),
27291            ))
27292        });
27293
27294    let _move = async |direction, count, cx: &mut EditorLspTestContext| {
27295        cx.update_editor(|editor, window, cx| {
27296            editor.go_to_reference_before_or_after_position(direction, count, window, cx)
27297        })
27298        .unwrap()
27299        .await
27300        .unwrap()
27301    };
27302
27303    _move(Direction::Next, 1, &mut cx).await;
27304    cx.assert_editor_state(CYCLE_POSITIONS[1]);
27305
27306    _move(Direction::Next, 1, &mut cx).await;
27307    cx.assert_editor_state(CYCLE_POSITIONS[2]);
27308
27309    _move(Direction::Next, 1, &mut cx).await;
27310    cx.assert_editor_state(CYCLE_POSITIONS[3]);
27311
27312    // loops back to the start
27313    _move(Direction::Next, 1, &mut cx).await;
27314    cx.assert_editor_state(CYCLE_POSITIONS[0]);
27315
27316    // loops back to the end
27317    _move(Direction::Prev, 1, &mut cx).await;
27318    cx.assert_editor_state(CYCLE_POSITIONS[3]);
27319
27320    _move(Direction::Prev, 1, &mut cx).await;
27321    cx.assert_editor_state(CYCLE_POSITIONS[2]);
27322
27323    _move(Direction::Prev, 1, &mut cx).await;
27324    cx.assert_editor_state(CYCLE_POSITIONS[1]);
27325
27326    _move(Direction::Prev, 1, &mut cx).await;
27327    cx.assert_editor_state(CYCLE_POSITIONS[0]);
27328
27329    _move(Direction::Next, 3, &mut cx).await;
27330    cx.assert_editor_state(CYCLE_POSITIONS[3]);
27331
27332    _move(Direction::Prev, 2, &mut cx).await;
27333    cx.assert_editor_state(CYCLE_POSITIONS[1]);
27334}