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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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
15059#[gpui::test]
15060async fn test_word_completions_disabled_with_no_provider(cx: &mut TestAppContext) {
15061    init_test(cx, |language_settings| {
15062        language_settings.defaults.completions = Some(CompletionSettingsContent {
15063            words: Some(WordsCompletionMode::Disabled),
15064            words_min_length: Some(0),
15065            lsp_insert_mode: Some(LspInsertMode::Insert),
15066            ..Default::default()
15067        });
15068    });
15069
15070    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15071    cx.update_editor(|editor, _, _| {
15072        editor.set_completion_provider(None);
15073    });
15074    cx.set_state(indoc! {"ˇ
15075        wow
15076        wowen
15077        wowser
15078    "});
15079    cx.simulate_keystroke("w");
15080    cx.executor().run_until_parked();
15081    cx.update_editor(|editor, _, _| {
15082        if editor.context_menu.borrow_mut().is_some() {
15083            panic!("expected completion menu to be hidden, as disabled in settings");
15084        }
15085    });
15086}
15087
15088fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
15089    let position = || lsp::Position {
15090        line: params.text_document_position.position.line,
15091        character: params.text_document_position.position.character,
15092    };
15093    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15094        range: lsp::Range {
15095            start: position(),
15096            end: position(),
15097        },
15098        new_text: text.to_string(),
15099    }))
15100}
15101
15102#[gpui::test]
15103async fn test_multiline_completion(cx: &mut TestAppContext) {
15104    init_test(cx, |_| {});
15105
15106    let fs = FakeFs::new(cx.executor());
15107    fs.insert_tree(
15108        path!("/a"),
15109        json!({
15110            "main.ts": "a",
15111        }),
15112    )
15113    .await;
15114
15115    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15116    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15117    let typescript_language = Arc::new(Language::new(
15118        LanguageConfig {
15119            name: "TypeScript".into(),
15120            matcher: LanguageMatcher {
15121                path_suffixes: vec!["ts".to_string()],
15122                ..LanguageMatcher::default()
15123            },
15124            line_comments: vec!["// ".into()],
15125            ..LanguageConfig::default()
15126        },
15127        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
15128    ));
15129    language_registry.add(typescript_language.clone());
15130    let mut fake_servers = language_registry.register_fake_lsp(
15131        "TypeScript",
15132        FakeLspAdapter {
15133            capabilities: lsp::ServerCapabilities {
15134                completion_provider: Some(lsp::CompletionOptions {
15135                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15136                    ..lsp::CompletionOptions::default()
15137                }),
15138                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15139                ..lsp::ServerCapabilities::default()
15140            },
15141            // Emulate vtsls label generation
15142            label_for_completion: Some(Box::new(|item, _| {
15143                let text = if let Some(description) = item
15144                    .label_details
15145                    .as_ref()
15146                    .and_then(|label_details| label_details.description.as_ref())
15147                {
15148                    format!("{} {}", item.label, description)
15149                } else if let Some(detail) = &item.detail {
15150                    format!("{} {}", item.label, detail)
15151                } else {
15152                    item.label.clone()
15153                };
15154                Some(language::CodeLabel::plain(text, None))
15155            })),
15156            ..FakeLspAdapter::default()
15157        },
15158    );
15159    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15160    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15161    let worktree_id = workspace
15162        .update(cx, |workspace, _window, cx| {
15163            workspace.project().update(cx, |project, cx| {
15164                project.worktrees(cx).next().unwrap().read(cx).id()
15165            })
15166        })
15167        .unwrap();
15168    let _buffer = project
15169        .update(cx, |project, cx| {
15170            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
15171        })
15172        .await
15173        .unwrap();
15174    let editor = workspace
15175        .update(cx, |workspace, window, cx| {
15176            workspace.open_path((worktree_id, rel_path("main.ts")), None, true, window, cx)
15177        })
15178        .unwrap()
15179        .await
15180        .unwrap()
15181        .downcast::<Editor>()
15182        .unwrap();
15183    let fake_server = fake_servers.next().await.unwrap();
15184
15185    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
15186    let multiline_label_2 = "a\nb\nc\n";
15187    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
15188    let multiline_description = "d\ne\nf\n";
15189    let multiline_detail_2 = "g\nh\ni\n";
15190
15191    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
15192        move |params, _| async move {
15193            Ok(Some(lsp::CompletionResponse::Array(vec![
15194                lsp::CompletionItem {
15195                    label: multiline_label.to_string(),
15196                    text_edit: gen_text_edit(&params, "new_text_1"),
15197                    ..lsp::CompletionItem::default()
15198                },
15199                lsp::CompletionItem {
15200                    label: "single line label 1".to_string(),
15201                    detail: Some(multiline_detail.to_string()),
15202                    text_edit: gen_text_edit(&params, "new_text_2"),
15203                    ..lsp::CompletionItem::default()
15204                },
15205                lsp::CompletionItem {
15206                    label: "single line label 2".to_string(),
15207                    label_details: Some(lsp::CompletionItemLabelDetails {
15208                        description: Some(multiline_description.to_string()),
15209                        detail: None,
15210                    }),
15211                    text_edit: gen_text_edit(&params, "new_text_2"),
15212                    ..lsp::CompletionItem::default()
15213                },
15214                lsp::CompletionItem {
15215                    label: multiline_label_2.to_string(),
15216                    detail: Some(multiline_detail_2.to_string()),
15217                    text_edit: gen_text_edit(&params, "new_text_3"),
15218                    ..lsp::CompletionItem::default()
15219                },
15220                lsp::CompletionItem {
15221                    label: "Label with many     spaces and \t but without newlines".to_string(),
15222                    detail: Some(
15223                        "Details with many     spaces and \t but without newlines".to_string(),
15224                    ),
15225                    text_edit: gen_text_edit(&params, "new_text_4"),
15226                    ..lsp::CompletionItem::default()
15227                },
15228            ])))
15229        },
15230    );
15231
15232    editor.update_in(cx, |editor, window, cx| {
15233        cx.focus_self(window);
15234        editor.move_to_end(&MoveToEnd, window, cx);
15235        editor.handle_input(".", window, cx);
15236    });
15237    cx.run_until_parked();
15238    completion_handle.next().await.unwrap();
15239
15240    editor.update(cx, |editor, _| {
15241        assert!(editor.context_menu_visible());
15242        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15243        {
15244            let completion_labels = menu
15245                .completions
15246                .borrow()
15247                .iter()
15248                .map(|c| c.label.text.clone())
15249                .collect::<Vec<_>>();
15250            assert_eq!(
15251                completion_labels,
15252                &[
15253                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
15254                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
15255                    "single line label 2 d e f ",
15256                    "a b c g h i ",
15257                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
15258                ],
15259                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
15260            );
15261
15262            for completion in menu
15263                .completions
15264                .borrow()
15265                .iter() {
15266                    assert_eq!(
15267                        completion.label.filter_range,
15268                        0..completion.label.text.len(),
15269                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
15270                    );
15271                }
15272        } else {
15273            panic!("expected completion menu to be open");
15274        }
15275    });
15276}
15277
15278#[gpui::test]
15279async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
15280    init_test(cx, |_| {});
15281    let mut cx = EditorLspTestContext::new_rust(
15282        lsp::ServerCapabilities {
15283            completion_provider: Some(lsp::CompletionOptions {
15284                trigger_characters: Some(vec![".".to_string()]),
15285                ..Default::default()
15286            }),
15287            ..Default::default()
15288        },
15289        cx,
15290    )
15291    .await;
15292    cx.lsp
15293        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15294            Ok(Some(lsp::CompletionResponse::Array(vec![
15295                lsp::CompletionItem {
15296                    label: "first".into(),
15297                    ..Default::default()
15298                },
15299                lsp::CompletionItem {
15300                    label: "last".into(),
15301                    ..Default::default()
15302                },
15303            ])))
15304        });
15305    cx.set_state("variableˇ");
15306    cx.simulate_keystroke(".");
15307    cx.executor().run_until_parked();
15308
15309    cx.update_editor(|editor, _, _| {
15310        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15311        {
15312            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
15313        } else {
15314            panic!("expected completion menu to be open");
15315        }
15316    });
15317
15318    cx.update_editor(|editor, window, cx| {
15319        editor.move_page_down(&MovePageDown::default(), window, cx);
15320        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15321        {
15322            assert!(
15323                menu.selected_item == 1,
15324                "expected PageDown to select the last item from the context menu"
15325            );
15326        } else {
15327            panic!("expected completion menu to stay open after PageDown");
15328        }
15329    });
15330
15331    cx.update_editor(|editor, window, cx| {
15332        editor.move_page_up(&MovePageUp::default(), window, cx);
15333        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15334        {
15335            assert!(
15336                menu.selected_item == 0,
15337                "expected PageUp to select the first item from the context menu"
15338            );
15339        } else {
15340            panic!("expected completion menu to stay open after PageUp");
15341        }
15342    });
15343}
15344
15345#[gpui::test]
15346async fn test_as_is_completions(cx: &mut TestAppContext) {
15347    init_test(cx, |_| {});
15348    let mut cx = EditorLspTestContext::new_rust(
15349        lsp::ServerCapabilities {
15350            completion_provider: Some(lsp::CompletionOptions {
15351                ..Default::default()
15352            }),
15353            ..Default::default()
15354        },
15355        cx,
15356    )
15357    .await;
15358    cx.lsp
15359        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15360            Ok(Some(lsp::CompletionResponse::Array(vec![
15361                lsp::CompletionItem {
15362                    label: "unsafe".into(),
15363                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15364                        range: lsp::Range {
15365                            start: lsp::Position {
15366                                line: 1,
15367                                character: 2,
15368                            },
15369                            end: lsp::Position {
15370                                line: 1,
15371                                character: 3,
15372                            },
15373                        },
15374                        new_text: "unsafe".to_string(),
15375                    })),
15376                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
15377                    ..Default::default()
15378                },
15379            ])))
15380        });
15381    cx.set_state("fn a() {}\n");
15382    cx.executor().run_until_parked();
15383    cx.update_editor(|editor, window, cx| {
15384        editor.trigger_completion_on_input("n", true, window, cx)
15385    });
15386    cx.executor().run_until_parked();
15387
15388    cx.update_editor(|editor, window, cx| {
15389        editor.confirm_completion(&Default::default(), window, cx)
15390    });
15391    cx.executor().run_until_parked();
15392    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
15393}
15394
15395#[gpui::test]
15396async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
15397    init_test(cx, |_| {});
15398    let language =
15399        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
15400    let mut cx = EditorLspTestContext::new(
15401        language,
15402        lsp::ServerCapabilities {
15403            completion_provider: Some(lsp::CompletionOptions {
15404                ..lsp::CompletionOptions::default()
15405            }),
15406            ..lsp::ServerCapabilities::default()
15407        },
15408        cx,
15409    )
15410    .await;
15411
15412    cx.set_state(
15413        "#ifndef BAR_H
15414#define BAR_H
15415
15416#include <stdbool.h>
15417
15418int fn_branch(bool do_branch1, bool do_branch2);
15419
15420#endif // BAR_H
15421ˇ",
15422    );
15423    cx.executor().run_until_parked();
15424    cx.update_editor(|editor, window, cx| {
15425        editor.handle_input("#", window, cx);
15426    });
15427    cx.executor().run_until_parked();
15428    cx.update_editor(|editor, window, cx| {
15429        editor.handle_input("i", window, cx);
15430    });
15431    cx.executor().run_until_parked();
15432    cx.update_editor(|editor, window, cx| {
15433        editor.handle_input("n", window, cx);
15434    });
15435    cx.executor().run_until_parked();
15436    cx.assert_editor_state(
15437        "#ifndef BAR_H
15438#define BAR_H
15439
15440#include <stdbool.h>
15441
15442int fn_branch(bool do_branch1, bool do_branch2);
15443
15444#endif // BAR_H
15445#inˇ",
15446    );
15447
15448    cx.lsp
15449        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15450            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15451                is_incomplete: false,
15452                item_defaults: None,
15453                items: vec![lsp::CompletionItem {
15454                    kind: Some(lsp::CompletionItemKind::SNIPPET),
15455                    label_details: Some(lsp::CompletionItemLabelDetails {
15456                        detail: Some("header".to_string()),
15457                        description: None,
15458                    }),
15459                    label: " include".to_string(),
15460                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15461                        range: lsp::Range {
15462                            start: lsp::Position {
15463                                line: 8,
15464                                character: 1,
15465                            },
15466                            end: lsp::Position {
15467                                line: 8,
15468                                character: 1,
15469                            },
15470                        },
15471                        new_text: "include \"$0\"".to_string(),
15472                    })),
15473                    sort_text: Some("40b67681include".to_string()),
15474                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15475                    filter_text: Some("include".to_string()),
15476                    insert_text: Some("include \"$0\"".to_string()),
15477                    ..lsp::CompletionItem::default()
15478                }],
15479            })))
15480        });
15481    cx.update_editor(|editor, window, cx| {
15482        editor.show_completions(&ShowCompletions, window, cx);
15483    });
15484    cx.executor().run_until_parked();
15485    cx.update_editor(|editor, window, cx| {
15486        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15487    });
15488    cx.executor().run_until_parked();
15489    cx.assert_editor_state(
15490        "#ifndef BAR_H
15491#define BAR_H
15492
15493#include <stdbool.h>
15494
15495int fn_branch(bool do_branch1, bool do_branch2);
15496
15497#endif // BAR_H
15498#include \"ˇ\"",
15499    );
15500
15501    cx.lsp
15502        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15503            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15504                is_incomplete: true,
15505                item_defaults: None,
15506                items: vec![lsp::CompletionItem {
15507                    kind: Some(lsp::CompletionItemKind::FILE),
15508                    label: "AGL/".to_string(),
15509                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15510                        range: lsp::Range {
15511                            start: lsp::Position {
15512                                line: 8,
15513                                character: 10,
15514                            },
15515                            end: lsp::Position {
15516                                line: 8,
15517                                character: 11,
15518                            },
15519                        },
15520                        new_text: "AGL/".to_string(),
15521                    })),
15522                    sort_text: Some("40b67681AGL/".to_string()),
15523                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15524                    filter_text: Some("AGL/".to_string()),
15525                    insert_text: Some("AGL/".to_string()),
15526                    ..lsp::CompletionItem::default()
15527                }],
15528            })))
15529        });
15530    cx.update_editor(|editor, window, cx| {
15531        editor.show_completions(&ShowCompletions, window, cx);
15532    });
15533    cx.executor().run_until_parked();
15534    cx.update_editor(|editor, window, cx| {
15535        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15536    });
15537    cx.executor().run_until_parked();
15538    cx.assert_editor_state(
15539        r##"#ifndef BAR_H
15540#define BAR_H
15541
15542#include <stdbool.h>
15543
15544int fn_branch(bool do_branch1, bool do_branch2);
15545
15546#endif // BAR_H
15547#include "AGL/ˇ"##,
15548    );
15549
15550    cx.update_editor(|editor, window, cx| {
15551        editor.handle_input("\"", window, cx);
15552    });
15553    cx.executor().run_until_parked();
15554    cx.assert_editor_state(
15555        r##"#ifndef BAR_H
15556#define BAR_H
15557
15558#include <stdbool.h>
15559
15560int fn_branch(bool do_branch1, bool do_branch2);
15561
15562#endif // BAR_H
15563#include "AGL/"ˇ"##,
15564    );
15565}
15566
15567#[gpui::test]
15568async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
15569    init_test(cx, |_| {});
15570
15571    let mut cx = EditorLspTestContext::new_rust(
15572        lsp::ServerCapabilities {
15573            completion_provider: Some(lsp::CompletionOptions {
15574                trigger_characters: Some(vec![".".to_string()]),
15575                resolve_provider: Some(true),
15576                ..Default::default()
15577            }),
15578            ..Default::default()
15579        },
15580        cx,
15581    )
15582    .await;
15583
15584    cx.set_state("fn main() { let a = 2ˇ; }");
15585    cx.simulate_keystroke(".");
15586    let completion_item = lsp::CompletionItem {
15587        label: "Some".into(),
15588        kind: Some(lsp::CompletionItemKind::SNIPPET),
15589        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15590        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15591            kind: lsp::MarkupKind::Markdown,
15592            value: "```rust\nSome(2)\n```".to_string(),
15593        })),
15594        deprecated: Some(false),
15595        sort_text: Some("Some".to_string()),
15596        filter_text: Some("Some".to_string()),
15597        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15598        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15599            range: lsp::Range {
15600                start: lsp::Position {
15601                    line: 0,
15602                    character: 22,
15603                },
15604                end: lsp::Position {
15605                    line: 0,
15606                    character: 22,
15607                },
15608            },
15609            new_text: "Some(2)".to_string(),
15610        })),
15611        additional_text_edits: Some(vec![lsp::TextEdit {
15612            range: lsp::Range {
15613                start: lsp::Position {
15614                    line: 0,
15615                    character: 20,
15616                },
15617                end: lsp::Position {
15618                    line: 0,
15619                    character: 22,
15620                },
15621            },
15622            new_text: "".to_string(),
15623        }]),
15624        ..Default::default()
15625    };
15626
15627    let closure_completion_item = completion_item.clone();
15628    let counter = Arc::new(AtomicUsize::new(0));
15629    let counter_clone = counter.clone();
15630    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15631        let task_completion_item = closure_completion_item.clone();
15632        counter_clone.fetch_add(1, atomic::Ordering::Release);
15633        async move {
15634            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15635                is_incomplete: true,
15636                item_defaults: None,
15637                items: vec![task_completion_item],
15638            })))
15639        }
15640    });
15641
15642    cx.condition(|editor, _| editor.context_menu_visible())
15643        .await;
15644    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
15645    assert!(request.next().await.is_some());
15646    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15647
15648    cx.simulate_keystrokes("S o m");
15649    cx.condition(|editor, _| editor.context_menu_visible())
15650        .await;
15651    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
15652    assert!(request.next().await.is_some());
15653    assert!(request.next().await.is_some());
15654    assert!(request.next().await.is_some());
15655    request.close();
15656    assert!(request.next().await.is_none());
15657    assert_eq!(
15658        counter.load(atomic::Ordering::Acquire),
15659        4,
15660        "With the completions menu open, only one LSP request should happen per input"
15661    );
15662}
15663
15664#[gpui::test]
15665async fn test_toggle_comment(cx: &mut TestAppContext) {
15666    init_test(cx, |_| {});
15667    let mut cx = EditorTestContext::new(cx).await;
15668    let language = Arc::new(Language::new(
15669        LanguageConfig {
15670            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15671            ..Default::default()
15672        },
15673        Some(tree_sitter_rust::LANGUAGE.into()),
15674    ));
15675    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15676
15677    // If multiple selections intersect a line, the line is only toggled once.
15678    cx.set_state(indoc! {"
15679        fn a() {
15680            «//b();
15681            ˇ»// «c();
15682            //ˇ»  d();
15683        }
15684    "});
15685
15686    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15687
15688    cx.assert_editor_state(indoc! {"
15689        fn a() {
15690            «b();
15691            c();
15692            ˇ» d();
15693        }
15694    "});
15695
15696    // The comment prefix is inserted at the same column for every line in a
15697    // selection.
15698    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15699
15700    cx.assert_editor_state(indoc! {"
15701        fn a() {
15702            // «b();
15703            // c();
15704            ˇ»//  d();
15705        }
15706    "});
15707
15708    // If a selection ends at the beginning of a line, that line is not toggled.
15709    cx.set_selections_state(indoc! {"
15710        fn a() {
15711            // b();
15712            «// c();
15713        ˇ»    //  d();
15714        }
15715    "});
15716
15717    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15718
15719    cx.assert_editor_state(indoc! {"
15720        fn a() {
15721            // b();
15722            «c();
15723        ˇ»    //  d();
15724        }
15725    "});
15726
15727    // If a selection span a single line and is empty, the line is toggled.
15728    cx.set_state(indoc! {"
15729        fn a() {
15730            a();
15731            b();
15732        ˇ
15733        }
15734    "});
15735
15736    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15737
15738    cx.assert_editor_state(indoc! {"
15739        fn a() {
15740            a();
15741            b();
15742        //•ˇ
15743        }
15744    "});
15745
15746    // If a selection span multiple lines, empty lines are not toggled.
15747    cx.set_state(indoc! {"
15748        fn a() {
15749            «a();
15750
15751            c();ˇ»
15752        }
15753    "});
15754
15755    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15756
15757    cx.assert_editor_state(indoc! {"
15758        fn a() {
15759            // «a();
15760
15761            // c();ˇ»
15762        }
15763    "});
15764
15765    // If a selection includes multiple comment prefixes, all lines are uncommented.
15766    cx.set_state(indoc! {"
15767        fn a() {
15768            «// a();
15769            /// b();
15770            //! c();ˇ»
15771        }
15772    "});
15773
15774    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15775
15776    cx.assert_editor_state(indoc! {"
15777        fn a() {
15778            «a();
15779            b();
15780            c();ˇ»
15781        }
15782    "});
15783}
15784
15785#[gpui::test]
15786async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
15787    init_test(cx, |_| {});
15788    let mut cx = EditorTestContext::new(cx).await;
15789    let language = Arc::new(Language::new(
15790        LanguageConfig {
15791            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15792            ..Default::default()
15793        },
15794        Some(tree_sitter_rust::LANGUAGE.into()),
15795    ));
15796    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15797
15798    let toggle_comments = &ToggleComments {
15799        advance_downwards: false,
15800        ignore_indent: true,
15801    };
15802
15803    // If multiple selections intersect a line, the line is only toggled once.
15804    cx.set_state(indoc! {"
15805        fn a() {
15806        //    «b();
15807        //    c();
15808        //    ˇ» d();
15809        }
15810    "});
15811
15812    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15813
15814    cx.assert_editor_state(indoc! {"
15815        fn a() {
15816            «b();
15817            c();
15818            ˇ» d();
15819        }
15820    "});
15821
15822    // The comment prefix is inserted at the beginning of each line
15823    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15824
15825    cx.assert_editor_state(indoc! {"
15826        fn a() {
15827        //    «b();
15828        //    c();
15829        //    ˇ» d();
15830        }
15831    "});
15832
15833    // If a selection ends at the beginning of a line, that line is not toggled.
15834    cx.set_selections_state(indoc! {"
15835        fn a() {
15836        //    b();
15837        //    «c();
15838        ˇ»//     d();
15839        }
15840    "});
15841
15842    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15843
15844    cx.assert_editor_state(indoc! {"
15845        fn a() {
15846        //    b();
15847            «c();
15848        ˇ»//     d();
15849        }
15850    "});
15851
15852    // If a selection span a single line and is empty, the line is toggled.
15853    cx.set_state(indoc! {"
15854        fn a() {
15855            a();
15856            b();
15857        ˇ
15858        }
15859    "});
15860
15861    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15862
15863    cx.assert_editor_state(indoc! {"
15864        fn a() {
15865            a();
15866            b();
15867        //ˇ
15868        }
15869    "});
15870
15871    // If a selection span multiple lines, empty lines are not toggled.
15872    cx.set_state(indoc! {"
15873        fn a() {
15874            «a();
15875
15876            c();ˇ»
15877        }
15878    "});
15879
15880    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15881
15882    cx.assert_editor_state(indoc! {"
15883        fn a() {
15884        //    «a();
15885
15886        //    c();ˇ»
15887        }
15888    "});
15889
15890    // If a selection includes multiple comment prefixes, all lines are uncommented.
15891    cx.set_state(indoc! {"
15892        fn a() {
15893        //    «a();
15894        ///    b();
15895        //!    c();ˇ»
15896        }
15897    "});
15898
15899    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15900
15901    cx.assert_editor_state(indoc! {"
15902        fn a() {
15903            «a();
15904            b();
15905            c();ˇ»
15906        }
15907    "});
15908}
15909
15910#[gpui::test]
15911async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
15912    init_test(cx, |_| {});
15913
15914    let language = Arc::new(Language::new(
15915        LanguageConfig {
15916            line_comments: vec!["// ".into()],
15917            ..Default::default()
15918        },
15919        Some(tree_sitter_rust::LANGUAGE.into()),
15920    ));
15921
15922    let mut cx = EditorTestContext::new(cx).await;
15923
15924    cx.language_registry().add(language.clone());
15925    cx.update_buffer(|buffer, cx| {
15926        buffer.set_language(Some(language), cx);
15927    });
15928
15929    let toggle_comments = &ToggleComments {
15930        advance_downwards: true,
15931        ignore_indent: false,
15932    };
15933
15934    // Single cursor on one line -> advance
15935    // Cursor moves horizontally 3 characters as well on non-blank line
15936    cx.set_state(indoc!(
15937        "fn a() {
15938             ˇdog();
15939             cat();
15940        }"
15941    ));
15942    cx.update_editor(|editor, window, cx| {
15943        editor.toggle_comments(toggle_comments, window, cx);
15944    });
15945    cx.assert_editor_state(indoc!(
15946        "fn a() {
15947             // dog();
15948             catˇ();
15949        }"
15950    ));
15951
15952    // Single selection on one line -> don't advance
15953    cx.set_state(indoc!(
15954        "fn a() {
15955             «dog()ˇ»;
15956             cat();
15957        }"
15958    ));
15959    cx.update_editor(|editor, window, cx| {
15960        editor.toggle_comments(toggle_comments, window, cx);
15961    });
15962    cx.assert_editor_state(indoc!(
15963        "fn a() {
15964             // «dog()ˇ»;
15965             cat();
15966        }"
15967    ));
15968
15969    // Multiple cursors on one line -> advance
15970    cx.set_state(indoc!(
15971        "fn a() {
15972             ˇdˇog();
15973             cat();
15974        }"
15975    ));
15976    cx.update_editor(|editor, window, cx| {
15977        editor.toggle_comments(toggle_comments, window, cx);
15978    });
15979    cx.assert_editor_state(indoc!(
15980        "fn a() {
15981             // dog();
15982             catˇ(ˇ);
15983        }"
15984    ));
15985
15986    // Multiple cursors on one line, with selection -> don't advance
15987    cx.set_state(indoc!(
15988        "fn a() {
15989             ˇdˇog«()ˇ»;
15990             cat();
15991        }"
15992    ));
15993    cx.update_editor(|editor, window, cx| {
15994        editor.toggle_comments(toggle_comments, window, cx);
15995    });
15996    cx.assert_editor_state(indoc!(
15997        "fn a() {
15998             // ˇdˇog«()ˇ»;
15999             cat();
16000        }"
16001    ));
16002
16003    // Single cursor on one line -> advance
16004    // Cursor moves to column 0 on blank line
16005    cx.set_state(indoc!(
16006        "fn a() {
16007             ˇdog();
16008
16009             cat();
16010        }"
16011    ));
16012    cx.update_editor(|editor, window, cx| {
16013        editor.toggle_comments(toggle_comments, window, cx);
16014    });
16015    cx.assert_editor_state(indoc!(
16016        "fn a() {
16017             // dog();
16018        ˇ
16019             cat();
16020        }"
16021    ));
16022
16023    // Single cursor on one line -> advance
16024    // Cursor starts and ends at column 0
16025    cx.set_state(indoc!(
16026        "fn a() {
16027         ˇ    dog();
16028             cat();
16029        }"
16030    ));
16031    cx.update_editor(|editor, window, cx| {
16032        editor.toggle_comments(toggle_comments, window, cx);
16033    });
16034    cx.assert_editor_state(indoc!(
16035        "fn a() {
16036             // dog();
16037         ˇ    cat();
16038        }"
16039    ));
16040}
16041
16042#[gpui::test]
16043async fn test_toggle_block_comment(cx: &mut TestAppContext) {
16044    init_test(cx, |_| {});
16045
16046    let mut cx = EditorTestContext::new(cx).await;
16047
16048    let html_language = Arc::new(
16049        Language::new(
16050            LanguageConfig {
16051                name: "HTML".into(),
16052                block_comment: Some(BlockCommentConfig {
16053                    start: "<!-- ".into(),
16054                    prefix: "".into(),
16055                    end: " -->".into(),
16056                    tab_size: 0,
16057                }),
16058                ..Default::default()
16059            },
16060            Some(tree_sitter_html::LANGUAGE.into()),
16061        )
16062        .with_injection_query(
16063            r#"
16064            (script_element
16065                (raw_text) @injection.content
16066                (#set! injection.language "javascript"))
16067            "#,
16068        )
16069        .unwrap(),
16070    );
16071
16072    let javascript_language = Arc::new(Language::new(
16073        LanguageConfig {
16074            name: "JavaScript".into(),
16075            line_comments: vec!["// ".into()],
16076            ..Default::default()
16077        },
16078        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
16079    ));
16080
16081    cx.language_registry().add(html_language.clone());
16082    cx.language_registry().add(javascript_language);
16083    cx.update_buffer(|buffer, cx| {
16084        buffer.set_language(Some(html_language), cx);
16085    });
16086
16087    // Toggle comments for empty selections
16088    cx.set_state(
16089        &r#"
16090            <p>A</p>ˇ
16091            <p>B</p>ˇ
16092            <p>C</p>ˇ
16093        "#
16094        .unindent(),
16095    );
16096    cx.update_editor(|editor, window, cx| {
16097        editor.toggle_comments(&ToggleComments::default(), window, cx)
16098    });
16099    cx.assert_editor_state(
16100        &r#"
16101            <!-- <p>A</p>ˇ -->
16102            <!-- <p>B</p>ˇ -->
16103            <!-- <p>C</p>ˇ -->
16104        "#
16105        .unindent(),
16106    );
16107    cx.update_editor(|editor, window, cx| {
16108        editor.toggle_comments(&ToggleComments::default(), window, cx)
16109    });
16110    cx.assert_editor_state(
16111        &r#"
16112            <p>A</p>ˇ
16113            <p>B</p>ˇ
16114            <p>C</p>ˇ
16115        "#
16116        .unindent(),
16117    );
16118
16119    // Toggle comments for mixture of empty and non-empty selections, where
16120    // multiple selections occupy a given line.
16121    cx.set_state(
16122        &r#"
16123            <p>A«</p>
16124            <p>ˇ»B</p>ˇ
16125            <p>C«</p>
16126            <p>ˇ»D</p>ˇ
16127        "#
16128        .unindent(),
16129    );
16130
16131    cx.update_editor(|editor, window, cx| {
16132        editor.toggle_comments(&ToggleComments::default(), window, cx)
16133    });
16134    cx.assert_editor_state(
16135        &r#"
16136            <!-- <p>A«</p>
16137            <p>ˇ»B</p>ˇ -->
16138            <!-- <p>C«</p>
16139            <p>ˇ»D</p>ˇ -->
16140        "#
16141        .unindent(),
16142    );
16143    cx.update_editor(|editor, window, cx| {
16144        editor.toggle_comments(&ToggleComments::default(), window, cx)
16145    });
16146    cx.assert_editor_state(
16147        &r#"
16148            <p>A«</p>
16149            <p>ˇ»B</p>ˇ
16150            <p>C«</p>
16151            <p>ˇ»D</p>ˇ
16152        "#
16153        .unindent(),
16154    );
16155
16156    // Toggle comments when different languages are active for different
16157    // selections.
16158    cx.set_state(
16159        &r#"
16160            ˇ<script>
16161                ˇvar x = new Y();
16162            ˇ</script>
16163        "#
16164        .unindent(),
16165    );
16166    cx.executor().run_until_parked();
16167    cx.update_editor(|editor, window, cx| {
16168        editor.toggle_comments(&ToggleComments::default(), window, cx)
16169    });
16170    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
16171    // Uncommenting and commenting from this position brings in even more wrong artifacts.
16172    cx.assert_editor_state(
16173        &r#"
16174            <!-- ˇ<script> -->
16175                // ˇvar x = new Y();
16176            <!-- ˇ</script> -->
16177        "#
16178        .unindent(),
16179    );
16180}
16181
16182#[gpui::test]
16183fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
16184    init_test(cx, |_| {});
16185
16186    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16187    let multibuffer = cx.new(|cx| {
16188        let mut multibuffer = MultiBuffer::new(ReadWrite);
16189        multibuffer.push_excerpts(
16190            buffer.clone(),
16191            [
16192                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
16193                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
16194            ],
16195            cx,
16196        );
16197        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
16198        multibuffer
16199    });
16200
16201    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16202    editor.update_in(cx, |editor, window, cx| {
16203        assert_eq!(editor.text(cx), "aaaa\nbbbb");
16204        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16205            s.select_ranges([
16206                Point::new(0, 0)..Point::new(0, 0),
16207                Point::new(1, 0)..Point::new(1, 0),
16208            ])
16209        });
16210
16211        editor.handle_input("X", window, cx);
16212        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
16213        assert_eq!(
16214            editor.selections.ranges(&editor.display_snapshot(cx)),
16215            [
16216                Point::new(0, 1)..Point::new(0, 1),
16217                Point::new(1, 1)..Point::new(1, 1),
16218            ]
16219        );
16220
16221        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
16222        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16223            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
16224        });
16225        editor.backspace(&Default::default(), window, cx);
16226        assert_eq!(editor.text(cx), "Xa\nbbb");
16227        assert_eq!(
16228            editor.selections.ranges(&editor.display_snapshot(cx)),
16229            [Point::new(1, 0)..Point::new(1, 0)]
16230        );
16231
16232        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16233            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
16234        });
16235        editor.backspace(&Default::default(), window, cx);
16236        assert_eq!(editor.text(cx), "X\nbb");
16237        assert_eq!(
16238            editor.selections.ranges(&editor.display_snapshot(cx)),
16239            [Point::new(0, 1)..Point::new(0, 1)]
16240        );
16241    });
16242}
16243
16244#[gpui::test]
16245fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
16246    init_test(cx, |_| {});
16247
16248    let markers = vec![('[', ']').into(), ('(', ')').into()];
16249    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
16250        indoc! {"
16251            [aaaa
16252            (bbbb]
16253            cccc)",
16254        },
16255        markers.clone(),
16256    );
16257    let excerpt_ranges = markers.into_iter().map(|marker| {
16258        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
16259        ExcerptRange::new(context)
16260    });
16261    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
16262    let multibuffer = cx.new(|cx| {
16263        let mut multibuffer = MultiBuffer::new(ReadWrite);
16264        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
16265        multibuffer
16266    });
16267
16268    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16269    editor.update_in(cx, |editor, window, cx| {
16270        let (expected_text, selection_ranges) = marked_text_ranges(
16271            indoc! {"
16272                aaaa
16273                bˇbbb
16274                bˇbbˇb
16275                cccc"
16276            },
16277            true,
16278        );
16279        assert_eq!(editor.text(cx), expected_text);
16280        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16281            s.select_ranges(selection_ranges)
16282        });
16283
16284        editor.handle_input("X", window, cx);
16285
16286        let (expected_text, expected_selections) = marked_text_ranges(
16287            indoc! {"
16288                aaaa
16289                bXˇbbXb
16290                bXˇbbXˇb
16291                cccc"
16292            },
16293            false,
16294        );
16295        assert_eq!(editor.text(cx), expected_text);
16296        assert_eq!(
16297            editor.selections.ranges(&editor.display_snapshot(cx)),
16298            expected_selections
16299        );
16300
16301        editor.newline(&Newline, window, cx);
16302        let (expected_text, expected_selections) = marked_text_ranges(
16303            indoc! {"
16304                aaaa
16305                bX
16306                ˇbbX
16307                b
16308                bX
16309                ˇbbX
16310                ˇb
16311                cccc"
16312            },
16313            false,
16314        );
16315        assert_eq!(editor.text(cx), expected_text);
16316        assert_eq!(
16317            editor.selections.ranges(&editor.display_snapshot(cx)),
16318            expected_selections
16319        );
16320    });
16321}
16322
16323#[gpui::test]
16324fn test_refresh_selections(cx: &mut TestAppContext) {
16325    init_test(cx, |_| {});
16326
16327    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16328    let mut excerpt1_id = None;
16329    let multibuffer = cx.new(|cx| {
16330        let mut multibuffer = MultiBuffer::new(ReadWrite);
16331        excerpt1_id = multibuffer
16332            .push_excerpts(
16333                buffer.clone(),
16334                [
16335                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16336                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16337                ],
16338                cx,
16339            )
16340            .into_iter()
16341            .next();
16342        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16343        multibuffer
16344    });
16345
16346    let editor = cx.add_window(|window, cx| {
16347        let mut editor = build_editor(multibuffer.clone(), window, cx);
16348        let snapshot = editor.snapshot(window, cx);
16349        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16350            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
16351        });
16352        editor.begin_selection(
16353            Point::new(2, 1).to_display_point(&snapshot),
16354            true,
16355            1,
16356            window,
16357            cx,
16358        );
16359        assert_eq!(
16360            editor.selections.ranges(&editor.display_snapshot(cx)),
16361            [
16362                Point::new(1, 3)..Point::new(1, 3),
16363                Point::new(2, 1)..Point::new(2, 1),
16364            ]
16365        );
16366        editor
16367    });
16368
16369    // Refreshing selections is a no-op when excerpts haven't changed.
16370    _ = editor.update(cx, |editor, window, cx| {
16371        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16372        assert_eq!(
16373            editor.selections.ranges(&editor.display_snapshot(cx)),
16374            [
16375                Point::new(1, 3)..Point::new(1, 3),
16376                Point::new(2, 1)..Point::new(2, 1),
16377            ]
16378        );
16379    });
16380
16381    multibuffer.update(cx, |multibuffer, cx| {
16382        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16383    });
16384    _ = editor.update(cx, |editor, window, cx| {
16385        // Removing an excerpt causes the first selection to become degenerate.
16386        assert_eq!(
16387            editor.selections.ranges(&editor.display_snapshot(cx)),
16388            [
16389                Point::new(0, 0)..Point::new(0, 0),
16390                Point::new(0, 1)..Point::new(0, 1)
16391            ]
16392        );
16393
16394        // Refreshing selections will relocate the first selection to the original buffer
16395        // location.
16396        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16397        assert_eq!(
16398            editor.selections.ranges(&editor.display_snapshot(cx)),
16399            [
16400                Point::new(0, 1)..Point::new(0, 1),
16401                Point::new(0, 3)..Point::new(0, 3)
16402            ]
16403        );
16404        assert!(editor.selections.pending_anchor().is_some());
16405    });
16406}
16407
16408#[gpui::test]
16409fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
16410    init_test(cx, |_| {});
16411
16412    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16413    let mut excerpt1_id = None;
16414    let multibuffer = cx.new(|cx| {
16415        let mut multibuffer = MultiBuffer::new(ReadWrite);
16416        excerpt1_id = multibuffer
16417            .push_excerpts(
16418                buffer.clone(),
16419                [
16420                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16421                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16422                ],
16423                cx,
16424            )
16425            .into_iter()
16426            .next();
16427        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16428        multibuffer
16429    });
16430
16431    let editor = cx.add_window(|window, cx| {
16432        let mut editor = build_editor(multibuffer.clone(), window, cx);
16433        let snapshot = editor.snapshot(window, cx);
16434        editor.begin_selection(
16435            Point::new(1, 3).to_display_point(&snapshot),
16436            false,
16437            1,
16438            window,
16439            cx,
16440        );
16441        assert_eq!(
16442            editor.selections.ranges(&editor.display_snapshot(cx)),
16443            [Point::new(1, 3)..Point::new(1, 3)]
16444        );
16445        editor
16446    });
16447
16448    multibuffer.update(cx, |multibuffer, cx| {
16449        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16450    });
16451    _ = editor.update(cx, |editor, window, cx| {
16452        assert_eq!(
16453            editor.selections.ranges(&editor.display_snapshot(cx)),
16454            [Point::new(0, 0)..Point::new(0, 0)]
16455        );
16456
16457        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
16458        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16459        assert_eq!(
16460            editor.selections.ranges(&editor.display_snapshot(cx)),
16461            [Point::new(0, 3)..Point::new(0, 3)]
16462        );
16463        assert!(editor.selections.pending_anchor().is_some());
16464    });
16465}
16466
16467#[gpui::test]
16468async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
16469    init_test(cx, |_| {});
16470
16471    let language = Arc::new(
16472        Language::new(
16473            LanguageConfig {
16474                brackets: BracketPairConfig {
16475                    pairs: vec![
16476                        BracketPair {
16477                            start: "{".to_string(),
16478                            end: "}".to_string(),
16479                            close: true,
16480                            surround: true,
16481                            newline: true,
16482                        },
16483                        BracketPair {
16484                            start: "/* ".to_string(),
16485                            end: " */".to_string(),
16486                            close: true,
16487                            surround: true,
16488                            newline: true,
16489                        },
16490                    ],
16491                    ..Default::default()
16492                },
16493                ..Default::default()
16494            },
16495            Some(tree_sitter_rust::LANGUAGE.into()),
16496        )
16497        .with_indents_query("")
16498        .unwrap(),
16499    );
16500
16501    let text = concat!(
16502        "{   }\n",     //
16503        "  x\n",       //
16504        "  /*   */\n", //
16505        "x\n",         //
16506        "{{} }\n",     //
16507    );
16508
16509    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
16510    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16511    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16512    editor
16513        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
16514        .await;
16515
16516    editor.update_in(cx, |editor, window, cx| {
16517        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16518            s.select_display_ranges([
16519                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
16520                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
16521                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
16522            ])
16523        });
16524        editor.newline(&Newline, window, cx);
16525
16526        assert_eq!(
16527            editor.buffer().read(cx).read(cx).text(),
16528            concat!(
16529                "{ \n",    // Suppress rustfmt
16530                "\n",      //
16531                "}\n",     //
16532                "  x\n",   //
16533                "  /* \n", //
16534                "  \n",    //
16535                "  */\n",  //
16536                "x\n",     //
16537                "{{} \n",  //
16538                "}\n",     //
16539            )
16540        );
16541    });
16542}
16543
16544#[gpui::test]
16545fn test_highlighted_ranges(cx: &mut TestAppContext) {
16546    init_test(cx, |_| {});
16547
16548    let editor = cx.add_window(|window, cx| {
16549        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
16550        build_editor(buffer, window, cx)
16551    });
16552
16553    _ = editor.update(cx, |editor, window, cx| {
16554        struct Type1;
16555        struct Type2;
16556
16557        let buffer = editor.buffer.read(cx).snapshot(cx);
16558
16559        let anchor_range =
16560            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
16561
16562        editor.highlight_background::<Type1>(
16563            &[
16564                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
16565                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
16566                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
16567                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
16568            ],
16569            |_| Hsla::red(),
16570            cx,
16571        );
16572        editor.highlight_background::<Type2>(
16573            &[
16574                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
16575                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
16576                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
16577                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
16578            ],
16579            |_| Hsla::green(),
16580            cx,
16581        );
16582
16583        let snapshot = editor.snapshot(window, cx);
16584        let highlighted_ranges = editor.sorted_background_highlights_in_range(
16585            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
16586            &snapshot,
16587            cx.theme(),
16588        );
16589        assert_eq!(
16590            highlighted_ranges,
16591            &[
16592                (
16593                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
16594                    Hsla::green(),
16595                ),
16596                (
16597                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
16598                    Hsla::red(),
16599                ),
16600                (
16601                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
16602                    Hsla::green(),
16603                ),
16604                (
16605                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16606                    Hsla::red(),
16607                ),
16608            ]
16609        );
16610        assert_eq!(
16611            editor.sorted_background_highlights_in_range(
16612                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
16613                &snapshot,
16614                cx.theme(),
16615            ),
16616            &[(
16617                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16618                Hsla::red(),
16619            )]
16620        );
16621    });
16622}
16623
16624#[gpui::test]
16625async fn test_following(cx: &mut TestAppContext) {
16626    init_test(cx, |_| {});
16627
16628    let fs = FakeFs::new(cx.executor());
16629    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16630
16631    let buffer = project.update(cx, |project, cx| {
16632        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
16633        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
16634    });
16635    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
16636    let follower = cx.update(|cx| {
16637        cx.open_window(
16638            WindowOptions {
16639                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
16640                    gpui::Point::new(px(0.), px(0.)),
16641                    gpui::Point::new(px(10.), px(80.)),
16642                ))),
16643                ..Default::default()
16644            },
16645            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
16646        )
16647        .unwrap()
16648    });
16649
16650    let is_still_following = Rc::new(RefCell::new(true));
16651    let follower_edit_event_count = Rc::new(RefCell::new(0));
16652    let pending_update = Rc::new(RefCell::new(None));
16653    let leader_entity = leader.root(cx).unwrap();
16654    let follower_entity = follower.root(cx).unwrap();
16655    _ = follower.update(cx, {
16656        let update = pending_update.clone();
16657        let is_still_following = is_still_following.clone();
16658        let follower_edit_event_count = follower_edit_event_count.clone();
16659        |_, window, cx| {
16660            cx.subscribe_in(
16661                &leader_entity,
16662                window,
16663                move |_, leader, event, window, cx| {
16664                    leader.read(cx).add_event_to_update_proto(
16665                        event,
16666                        &mut update.borrow_mut(),
16667                        window,
16668                        cx,
16669                    );
16670                },
16671            )
16672            .detach();
16673
16674            cx.subscribe_in(
16675                &follower_entity,
16676                window,
16677                move |_, _, event: &EditorEvent, _window, _cx| {
16678                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
16679                        *is_still_following.borrow_mut() = false;
16680                    }
16681
16682                    if let EditorEvent::BufferEdited = event {
16683                        *follower_edit_event_count.borrow_mut() += 1;
16684                    }
16685                },
16686            )
16687            .detach();
16688        }
16689    });
16690
16691    // Update the selections only
16692    _ = leader.update(cx, |leader, window, cx| {
16693        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16694            s.select_ranges([1..1])
16695        });
16696    });
16697    follower
16698        .update(cx, |follower, window, cx| {
16699            follower.apply_update_proto(
16700                &project,
16701                pending_update.borrow_mut().take().unwrap(),
16702                window,
16703                cx,
16704            )
16705        })
16706        .unwrap()
16707        .await
16708        .unwrap();
16709    _ = follower.update(cx, |follower, _, cx| {
16710        assert_eq!(
16711            follower.selections.ranges(&follower.display_snapshot(cx)),
16712            vec![1..1]
16713        );
16714    });
16715    assert!(*is_still_following.borrow());
16716    assert_eq!(*follower_edit_event_count.borrow(), 0);
16717
16718    // Update the scroll position only
16719    _ = leader.update(cx, |leader, window, cx| {
16720        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16721    });
16722    follower
16723        .update(cx, |follower, window, cx| {
16724            follower.apply_update_proto(
16725                &project,
16726                pending_update.borrow_mut().take().unwrap(),
16727                window,
16728                cx,
16729            )
16730        })
16731        .unwrap()
16732        .await
16733        .unwrap();
16734    assert_eq!(
16735        follower
16736            .update(cx, |follower, _, cx| follower.scroll_position(cx))
16737            .unwrap(),
16738        gpui::Point::new(1.5, 3.5)
16739    );
16740    assert!(*is_still_following.borrow());
16741    assert_eq!(*follower_edit_event_count.borrow(), 0);
16742
16743    // Update the selections and scroll position. The follower's scroll position is updated
16744    // via autoscroll, not via the leader's exact scroll position.
16745    _ = leader.update(cx, |leader, window, cx| {
16746        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16747            s.select_ranges([0..0])
16748        });
16749        leader.request_autoscroll(Autoscroll::newest(), cx);
16750        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16751    });
16752    follower
16753        .update(cx, |follower, window, cx| {
16754            follower.apply_update_proto(
16755                &project,
16756                pending_update.borrow_mut().take().unwrap(),
16757                window,
16758                cx,
16759            )
16760        })
16761        .unwrap()
16762        .await
16763        .unwrap();
16764    _ = follower.update(cx, |follower, _, cx| {
16765        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
16766        assert_eq!(
16767            follower.selections.ranges(&follower.display_snapshot(cx)),
16768            vec![0..0]
16769        );
16770    });
16771    assert!(*is_still_following.borrow());
16772
16773    // Creating a pending selection that precedes another selection
16774    _ = leader.update(cx, |leader, window, cx| {
16775        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16776            s.select_ranges([1..1])
16777        });
16778        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
16779    });
16780    follower
16781        .update(cx, |follower, window, cx| {
16782            follower.apply_update_proto(
16783                &project,
16784                pending_update.borrow_mut().take().unwrap(),
16785                window,
16786                cx,
16787            )
16788        })
16789        .unwrap()
16790        .await
16791        .unwrap();
16792    _ = follower.update(cx, |follower, _, cx| {
16793        assert_eq!(
16794            follower.selections.ranges(&follower.display_snapshot(cx)),
16795            vec![0..0, 1..1]
16796        );
16797    });
16798    assert!(*is_still_following.borrow());
16799
16800    // Extend the pending selection so that it surrounds another selection
16801    _ = leader.update(cx, |leader, window, cx| {
16802        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
16803    });
16804    follower
16805        .update(cx, |follower, window, cx| {
16806            follower.apply_update_proto(
16807                &project,
16808                pending_update.borrow_mut().take().unwrap(),
16809                window,
16810                cx,
16811            )
16812        })
16813        .unwrap()
16814        .await
16815        .unwrap();
16816    _ = follower.update(cx, |follower, _, cx| {
16817        assert_eq!(
16818            follower.selections.ranges(&follower.display_snapshot(cx)),
16819            vec![0..2]
16820        );
16821    });
16822
16823    // Scrolling locally breaks the follow
16824    _ = follower.update(cx, |follower, window, cx| {
16825        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
16826        follower.set_scroll_anchor(
16827            ScrollAnchor {
16828                anchor: top_anchor,
16829                offset: gpui::Point::new(0.0, 0.5),
16830            },
16831            window,
16832            cx,
16833        );
16834    });
16835    assert!(!(*is_still_following.borrow()));
16836}
16837
16838#[gpui::test]
16839async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
16840    init_test(cx, |_| {});
16841
16842    let fs = FakeFs::new(cx.executor());
16843    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16844    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16845    let pane = workspace
16846        .update(cx, |workspace, _, _| workspace.active_pane().clone())
16847        .unwrap();
16848
16849    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16850
16851    let leader = pane.update_in(cx, |_, window, cx| {
16852        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
16853        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
16854    });
16855
16856    // Start following the editor when it has no excerpts.
16857    let mut state_message =
16858        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16859    let workspace_entity = workspace.root(cx).unwrap();
16860    let follower_1 = cx
16861        .update_window(*workspace.deref(), |_, window, cx| {
16862            Editor::from_state_proto(
16863                workspace_entity,
16864                ViewId {
16865                    creator: CollaboratorId::PeerId(PeerId::default()),
16866                    id: 0,
16867                },
16868                &mut state_message,
16869                window,
16870                cx,
16871            )
16872        })
16873        .unwrap()
16874        .unwrap()
16875        .await
16876        .unwrap();
16877
16878    let update_message = Rc::new(RefCell::new(None));
16879    follower_1.update_in(cx, {
16880        let update = update_message.clone();
16881        |_, window, cx| {
16882            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
16883                leader.read(cx).add_event_to_update_proto(
16884                    event,
16885                    &mut update.borrow_mut(),
16886                    window,
16887                    cx,
16888                );
16889            })
16890            .detach();
16891        }
16892    });
16893
16894    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
16895        (
16896            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
16897            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
16898        )
16899    });
16900
16901    // Insert some excerpts.
16902    leader.update(cx, |leader, cx| {
16903        leader.buffer.update(cx, |multibuffer, cx| {
16904            multibuffer.set_excerpts_for_path(
16905                PathKey::with_sort_prefix(1, rel_path("b.txt").into_arc()),
16906                buffer_1.clone(),
16907                vec![
16908                    Point::row_range(0..3),
16909                    Point::row_range(1..6),
16910                    Point::row_range(12..15),
16911                ],
16912                0,
16913                cx,
16914            );
16915            multibuffer.set_excerpts_for_path(
16916                PathKey::with_sort_prefix(1, rel_path("a.txt").into_arc()),
16917                buffer_2.clone(),
16918                vec![Point::row_range(0..6), Point::row_range(8..12)],
16919                0,
16920                cx,
16921            );
16922        });
16923    });
16924
16925    // Apply the update of adding the excerpts.
16926    follower_1
16927        .update_in(cx, |follower, window, cx| {
16928            follower.apply_update_proto(
16929                &project,
16930                update_message.borrow().clone().unwrap(),
16931                window,
16932                cx,
16933            )
16934        })
16935        .await
16936        .unwrap();
16937    assert_eq!(
16938        follower_1.update(cx, |editor, cx| editor.text(cx)),
16939        leader.update(cx, |editor, cx| editor.text(cx))
16940    );
16941    update_message.borrow_mut().take();
16942
16943    // Start following separately after it already has excerpts.
16944    let mut state_message =
16945        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16946    let workspace_entity = workspace.root(cx).unwrap();
16947    let follower_2 = cx
16948        .update_window(*workspace.deref(), |_, window, cx| {
16949            Editor::from_state_proto(
16950                workspace_entity,
16951                ViewId {
16952                    creator: CollaboratorId::PeerId(PeerId::default()),
16953                    id: 0,
16954                },
16955                &mut state_message,
16956                window,
16957                cx,
16958            )
16959        })
16960        .unwrap()
16961        .unwrap()
16962        .await
16963        .unwrap();
16964    assert_eq!(
16965        follower_2.update(cx, |editor, cx| editor.text(cx)),
16966        leader.update(cx, |editor, cx| editor.text(cx))
16967    );
16968
16969    // Remove some excerpts.
16970    leader.update(cx, |leader, cx| {
16971        leader.buffer.update(cx, |multibuffer, cx| {
16972            let excerpt_ids = multibuffer.excerpt_ids();
16973            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
16974            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
16975        });
16976    });
16977
16978    // Apply the update of removing the excerpts.
16979    follower_1
16980        .update_in(cx, |follower, window, cx| {
16981            follower.apply_update_proto(
16982                &project,
16983                update_message.borrow().clone().unwrap(),
16984                window,
16985                cx,
16986            )
16987        })
16988        .await
16989        .unwrap();
16990    follower_2
16991        .update_in(cx, |follower, window, cx| {
16992            follower.apply_update_proto(
16993                &project,
16994                update_message.borrow().clone().unwrap(),
16995                window,
16996                cx,
16997            )
16998        })
16999        .await
17000        .unwrap();
17001    update_message.borrow_mut().take();
17002    assert_eq!(
17003        follower_1.update(cx, |editor, cx| editor.text(cx)),
17004        leader.update(cx, |editor, cx| editor.text(cx))
17005    );
17006}
17007
17008#[gpui::test]
17009async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17010    init_test(cx, |_| {});
17011
17012    let mut cx = EditorTestContext::new(cx).await;
17013    let lsp_store =
17014        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
17015
17016    cx.set_state(indoc! {"
17017        ˇfn func(abc def: i32) -> u32 {
17018        }
17019    "});
17020
17021    cx.update(|_, cx| {
17022        lsp_store.update(cx, |lsp_store, cx| {
17023            lsp_store
17024                .update_diagnostics(
17025                    LanguageServerId(0),
17026                    lsp::PublishDiagnosticsParams {
17027                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
17028                        version: None,
17029                        diagnostics: vec![
17030                            lsp::Diagnostic {
17031                                range: lsp::Range::new(
17032                                    lsp::Position::new(0, 11),
17033                                    lsp::Position::new(0, 12),
17034                                ),
17035                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17036                                ..Default::default()
17037                            },
17038                            lsp::Diagnostic {
17039                                range: lsp::Range::new(
17040                                    lsp::Position::new(0, 12),
17041                                    lsp::Position::new(0, 15),
17042                                ),
17043                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17044                                ..Default::default()
17045                            },
17046                            lsp::Diagnostic {
17047                                range: lsp::Range::new(
17048                                    lsp::Position::new(0, 25),
17049                                    lsp::Position::new(0, 28),
17050                                ),
17051                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17052                                ..Default::default()
17053                            },
17054                        ],
17055                    },
17056                    None,
17057                    DiagnosticSourceKind::Pushed,
17058                    &[],
17059                    cx,
17060                )
17061                .unwrap()
17062        });
17063    });
17064
17065    executor.run_until_parked();
17066
17067    cx.update_editor(|editor, window, cx| {
17068        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17069    });
17070
17071    cx.assert_editor_state(indoc! {"
17072        fn func(abc def: i32) -> ˇu32 {
17073        }
17074    "});
17075
17076    cx.update_editor(|editor, window, cx| {
17077        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17078    });
17079
17080    cx.assert_editor_state(indoc! {"
17081        fn func(abc ˇdef: i32) -> u32 {
17082        }
17083    "});
17084
17085    cx.update_editor(|editor, window, cx| {
17086        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17087    });
17088
17089    cx.assert_editor_state(indoc! {"
17090        fn func(abcˇ def: i32) -> u32 {
17091        }
17092    "});
17093
17094    cx.update_editor(|editor, window, cx| {
17095        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17096    });
17097
17098    cx.assert_editor_state(indoc! {"
17099        fn func(abc def: i32) -> ˇu32 {
17100        }
17101    "});
17102}
17103
17104#[gpui::test]
17105async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17106    init_test(cx, |_| {});
17107
17108    let mut cx = EditorTestContext::new(cx).await;
17109
17110    let diff_base = r#"
17111        use some::mod;
17112
17113        const A: u32 = 42;
17114
17115        fn main() {
17116            println!("hello");
17117
17118            println!("world");
17119        }
17120        "#
17121    .unindent();
17122
17123    // Edits are modified, removed, modified, added
17124    cx.set_state(
17125        &r#"
17126        use some::modified;
17127
17128        ˇ
17129        fn main() {
17130            println!("hello there");
17131
17132            println!("around the");
17133            println!("world");
17134        }
17135        "#
17136        .unindent(),
17137    );
17138
17139    cx.set_head_text(&diff_base);
17140    executor.run_until_parked();
17141
17142    cx.update_editor(|editor, window, cx| {
17143        //Wrap around the bottom of the buffer
17144        for _ in 0..3 {
17145            editor.go_to_next_hunk(&GoToHunk, window, cx);
17146        }
17147    });
17148
17149    cx.assert_editor_state(
17150        &r#"
17151        ˇuse some::modified;
17152
17153
17154        fn main() {
17155            println!("hello there");
17156
17157            println!("around the");
17158            println!("world");
17159        }
17160        "#
17161        .unindent(),
17162    );
17163
17164    cx.update_editor(|editor, window, cx| {
17165        //Wrap around the top of the buffer
17166        for _ in 0..2 {
17167            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17168        }
17169    });
17170
17171    cx.assert_editor_state(
17172        &r#"
17173        use some::modified;
17174
17175
17176        fn main() {
17177        ˇ    println!("hello there");
17178
17179            println!("around the");
17180            println!("world");
17181        }
17182        "#
17183        .unindent(),
17184    );
17185
17186    cx.update_editor(|editor, window, cx| {
17187        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17188    });
17189
17190    cx.assert_editor_state(
17191        &r#"
17192        use some::modified;
17193
17194        ˇ
17195        fn main() {
17196            println!("hello there");
17197
17198            println!("around the");
17199            println!("world");
17200        }
17201        "#
17202        .unindent(),
17203    );
17204
17205    cx.update_editor(|editor, window, cx| {
17206        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17207    });
17208
17209    cx.assert_editor_state(
17210        &r#"
17211        ˇuse some::modified;
17212
17213
17214        fn main() {
17215            println!("hello there");
17216
17217            println!("around the");
17218            println!("world");
17219        }
17220        "#
17221        .unindent(),
17222    );
17223
17224    cx.update_editor(|editor, window, cx| {
17225        for _ in 0..2 {
17226            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17227        }
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    cx.update_editor(|editor, window, cx| {
17246        editor.fold(&Fold, window, cx);
17247    });
17248
17249    cx.update_editor(|editor, window, cx| {
17250        editor.go_to_next_hunk(&GoToHunk, window, cx);
17251    });
17252
17253    cx.assert_editor_state(
17254        &r#"
17255        ˇuse some::modified;
17256
17257
17258        fn main() {
17259            println!("hello there");
17260
17261            println!("around the");
17262            println!("world");
17263        }
17264        "#
17265        .unindent(),
17266    );
17267}
17268
17269#[test]
17270fn test_split_words() {
17271    fn split(text: &str) -> Vec<&str> {
17272        split_words(text).collect()
17273    }
17274
17275    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
17276    assert_eq!(split("hello_world"), &["hello_", "world"]);
17277    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
17278    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
17279    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
17280    assert_eq!(split("helloworld"), &["helloworld"]);
17281
17282    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
17283}
17284
17285#[gpui::test]
17286async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
17287    init_test(cx, |_| {});
17288
17289    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
17290    let mut assert = |before, after| {
17291        let _state_context = cx.set_state(before);
17292        cx.run_until_parked();
17293        cx.update_editor(|editor, window, cx| {
17294            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
17295        });
17296        cx.run_until_parked();
17297        cx.assert_editor_state(after);
17298    };
17299
17300    // Outside bracket jumps to outside of matching bracket
17301    assert("console.logˇ(var);", "console.log(var)ˇ;");
17302    assert("console.log(var)ˇ;", "console.logˇ(var);");
17303
17304    // Inside bracket jumps to inside of matching bracket
17305    assert("console.log(ˇvar);", "console.log(varˇ);");
17306    assert("console.log(varˇ);", "console.log(ˇvar);");
17307
17308    // When outside a bracket and inside, favor jumping to the inside bracket
17309    assert(
17310        "console.log('foo', [1, 2, 3]ˇ);",
17311        "console.log(ˇ'foo', [1, 2, 3]);",
17312    );
17313    assert(
17314        "console.log(ˇ'foo', [1, 2, 3]);",
17315        "console.log('foo', [1, 2, 3]ˇ);",
17316    );
17317
17318    // Bias forward if two options are equally likely
17319    assert(
17320        "let result = curried_fun()ˇ();",
17321        "let result = curried_fun()()ˇ;",
17322    );
17323
17324    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
17325    assert(
17326        indoc! {"
17327            function test() {
17328                console.log('test')ˇ
17329            }"},
17330        indoc! {"
17331            function test() {
17332                console.logˇ('test')
17333            }"},
17334    );
17335}
17336
17337#[gpui::test]
17338async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
17339    init_test(cx, |_| {});
17340
17341    let fs = FakeFs::new(cx.executor());
17342    fs.insert_tree(
17343        path!("/a"),
17344        json!({
17345            "main.rs": "fn main() { let a = 5; }",
17346            "other.rs": "// Test file",
17347        }),
17348    )
17349    .await;
17350    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17351
17352    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17353    language_registry.add(Arc::new(Language::new(
17354        LanguageConfig {
17355            name: "Rust".into(),
17356            matcher: LanguageMatcher {
17357                path_suffixes: vec!["rs".to_string()],
17358                ..Default::default()
17359            },
17360            brackets: BracketPairConfig {
17361                pairs: vec![BracketPair {
17362                    start: "{".to_string(),
17363                    end: "}".to_string(),
17364                    close: true,
17365                    surround: true,
17366                    newline: true,
17367                }],
17368                disabled_scopes_by_bracket_ix: Vec::new(),
17369            },
17370            ..Default::default()
17371        },
17372        Some(tree_sitter_rust::LANGUAGE.into()),
17373    )));
17374    let mut fake_servers = language_registry.register_fake_lsp(
17375        "Rust",
17376        FakeLspAdapter {
17377            capabilities: lsp::ServerCapabilities {
17378                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17379                    first_trigger_character: "{".to_string(),
17380                    more_trigger_character: None,
17381                }),
17382                ..Default::default()
17383            },
17384            ..Default::default()
17385        },
17386    );
17387
17388    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17389
17390    let cx = &mut VisualTestContext::from_window(*workspace, cx);
17391
17392    let worktree_id = workspace
17393        .update(cx, |workspace, _, cx| {
17394            workspace.project().update(cx, |project, cx| {
17395                project.worktrees(cx).next().unwrap().read(cx).id()
17396            })
17397        })
17398        .unwrap();
17399
17400    let buffer = project
17401        .update(cx, |project, cx| {
17402            project.open_local_buffer(path!("/a/main.rs"), cx)
17403        })
17404        .await
17405        .unwrap();
17406    let editor_handle = workspace
17407        .update(cx, |workspace, window, cx| {
17408            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
17409        })
17410        .unwrap()
17411        .await
17412        .unwrap()
17413        .downcast::<Editor>()
17414        .unwrap();
17415
17416    cx.executor().start_waiting();
17417    let fake_server = fake_servers.next().await.unwrap();
17418
17419    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
17420        |params, _| async move {
17421            assert_eq!(
17422                params.text_document_position.text_document.uri,
17423                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
17424            );
17425            assert_eq!(
17426                params.text_document_position.position,
17427                lsp::Position::new(0, 21),
17428            );
17429
17430            Ok(Some(vec![lsp::TextEdit {
17431                new_text: "]".to_string(),
17432                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17433            }]))
17434        },
17435    );
17436
17437    editor_handle.update_in(cx, |editor, window, cx| {
17438        window.focus(&editor.focus_handle(cx));
17439        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17440            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
17441        });
17442        editor.handle_input("{", window, cx);
17443    });
17444
17445    cx.executor().run_until_parked();
17446
17447    buffer.update(cx, |buffer, _| {
17448        assert_eq!(
17449            buffer.text(),
17450            "fn main() { let a = {5}; }",
17451            "No extra braces from on type formatting should appear in the buffer"
17452        )
17453    });
17454}
17455
17456#[gpui::test(iterations = 20, seeds(31))]
17457async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
17458    init_test(cx, |_| {});
17459
17460    let mut cx = EditorLspTestContext::new_rust(
17461        lsp::ServerCapabilities {
17462            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17463                first_trigger_character: ".".to_string(),
17464                more_trigger_character: None,
17465            }),
17466            ..Default::default()
17467        },
17468        cx,
17469    )
17470    .await;
17471
17472    cx.update_buffer(|buffer, _| {
17473        // This causes autoindent to be async.
17474        buffer.set_sync_parse_timeout(Duration::ZERO)
17475    });
17476
17477    cx.set_state("fn c() {\n    d()ˇ\n}\n");
17478    cx.simulate_keystroke("\n");
17479    cx.run_until_parked();
17480
17481    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
17482    let mut request =
17483        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
17484            let buffer_cloned = buffer_cloned.clone();
17485            async move {
17486                buffer_cloned.update(&mut cx, |buffer, _| {
17487                    assert_eq!(
17488                        buffer.text(),
17489                        "fn c() {\n    d()\n        .\n}\n",
17490                        "OnTypeFormatting should triggered after autoindent applied"
17491                    )
17492                })?;
17493
17494                Ok(Some(vec![]))
17495            }
17496        });
17497
17498    cx.simulate_keystroke(".");
17499    cx.run_until_parked();
17500
17501    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
17502    assert!(request.next().await.is_some());
17503    request.close();
17504    assert!(request.next().await.is_none());
17505}
17506
17507#[gpui::test]
17508async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
17509    init_test(cx, |_| {});
17510
17511    let fs = FakeFs::new(cx.executor());
17512    fs.insert_tree(
17513        path!("/a"),
17514        json!({
17515            "main.rs": "fn main() { let a = 5; }",
17516            "other.rs": "// Test file",
17517        }),
17518    )
17519    .await;
17520
17521    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17522
17523    let server_restarts = Arc::new(AtomicUsize::new(0));
17524    let closure_restarts = Arc::clone(&server_restarts);
17525    let language_server_name = "test language server";
17526    let language_name: LanguageName = "Rust".into();
17527
17528    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17529    language_registry.add(Arc::new(Language::new(
17530        LanguageConfig {
17531            name: language_name.clone(),
17532            matcher: LanguageMatcher {
17533                path_suffixes: vec!["rs".to_string()],
17534                ..Default::default()
17535            },
17536            ..Default::default()
17537        },
17538        Some(tree_sitter_rust::LANGUAGE.into()),
17539    )));
17540    let mut fake_servers = language_registry.register_fake_lsp(
17541        "Rust",
17542        FakeLspAdapter {
17543            name: language_server_name,
17544            initialization_options: Some(json!({
17545                "testOptionValue": true
17546            })),
17547            initializer: Some(Box::new(move |fake_server| {
17548                let task_restarts = Arc::clone(&closure_restarts);
17549                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
17550                    task_restarts.fetch_add(1, atomic::Ordering::Release);
17551                    futures::future::ready(Ok(()))
17552                });
17553            })),
17554            ..Default::default()
17555        },
17556    );
17557
17558    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17559    let _buffer = project
17560        .update(cx, |project, cx| {
17561            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
17562        })
17563        .await
17564        .unwrap();
17565    let _fake_server = fake_servers.next().await.unwrap();
17566    update_test_language_settings(cx, |language_settings| {
17567        language_settings.languages.0.insert(
17568            language_name.clone().0,
17569            LanguageSettingsContent {
17570                tab_size: NonZeroU32::new(8),
17571                ..Default::default()
17572            },
17573        );
17574    });
17575    cx.executor().run_until_parked();
17576    assert_eq!(
17577        server_restarts.load(atomic::Ordering::Acquire),
17578        0,
17579        "Should not restart LSP server on an unrelated change"
17580    );
17581
17582    update_test_project_settings(cx, |project_settings| {
17583        project_settings.lsp.insert(
17584            "Some other server name".into(),
17585            LspSettings {
17586                binary: None,
17587                settings: None,
17588                initialization_options: Some(json!({
17589                    "some other init value": false
17590                })),
17591                enable_lsp_tasks: false,
17592                fetch: None,
17593            },
17594        );
17595    });
17596    cx.executor().run_until_parked();
17597    assert_eq!(
17598        server_restarts.load(atomic::Ordering::Acquire),
17599        0,
17600        "Should not restart LSP server on an unrelated LSP settings change"
17601    );
17602
17603    update_test_project_settings(cx, |project_settings| {
17604        project_settings.lsp.insert(
17605            language_server_name.into(),
17606            LspSettings {
17607                binary: None,
17608                settings: None,
17609                initialization_options: Some(json!({
17610                    "anotherInitValue": false
17611                })),
17612                enable_lsp_tasks: false,
17613                fetch: None,
17614            },
17615        );
17616    });
17617    cx.executor().run_until_parked();
17618    assert_eq!(
17619        server_restarts.load(atomic::Ordering::Acquire),
17620        1,
17621        "Should restart LSP server on a related LSP settings change"
17622    );
17623
17624    update_test_project_settings(cx, |project_settings| {
17625        project_settings.lsp.insert(
17626            language_server_name.into(),
17627            LspSettings {
17628                binary: None,
17629                settings: None,
17630                initialization_options: Some(json!({
17631                    "anotherInitValue": false
17632                })),
17633                enable_lsp_tasks: false,
17634                fetch: None,
17635            },
17636        );
17637    });
17638    cx.executor().run_until_parked();
17639    assert_eq!(
17640        server_restarts.load(atomic::Ordering::Acquire),
17641        1,
17642        "Should not restart LSP server on a related LSP settings change that is the same"
17643    );
17644
17645    update_test_project_settings(cx, |project_settings| {
17646        project_settings.lsp.insert(
17647            language_server_name.into(),
17648            LspSettings {
17649                binary: None,
17650                settings: None,
17651                initialization_options: None,
17652                enable_lsp_tasks: false,
17653                fetch: None,
17654            },
17655        );
17656    });
17657    cx.executor().run_until_parked();
17658    assert_eq!(
17659        server_restarts.load(atomic::Ordering::Acquire),
17660        2,
17661        "Should restart LSP server on another related LSP settings change"
17662    );
17663}
17664
17665#[gpui::test]
17666async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
17667    init_test(cx, |_| {});
17668
17669    let mut cx = EditorLspTestContext::new_rust(
17670        lsp::ServerCapabilities {
17671            completion_provider: Some(lsp::CompletionOptions {
17672                trigger_characters: Some(vec![".".to_string()]),
17673                resolve_provider: Some(true),
17674                ..Default::default()
17675            }),
17676            ..Default::default()
17677        },
17678        cx,
17679    )
17680    .await;
17681
17682    cx.set_state("fn main() { let a = 2ˇ; }");
17683    cx.simulate_keystroke(".");
17684    let completion_item = lsp::CompletionItem {
17685        label: "some".into(),
17686        kind: Some(lsp::CompletionItemKind::SNIPPET),
17687        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
17688        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
17689            kind: lsp::MarkupKind::Markdown,
17690            value: "```rust\nSome(2)\n```".to_string(),
17691        })),
17692        deprecated: Some(false),
17693        sort_text: Some("fffffff2".to_string()),
17694        filter_text: Some("some".to_string()),
17695        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
17696        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17697            range: lsp::Range {
17698                start: lsp::Position {
17699                    line: 0,
17700                    character: 22,
17701                },
17702                end: lsp::Position {
17703                    line: 0,
17704                    character: 22,
17705                },
17706            },
17707            new_text: "Some(2)".to_string(),
17708        })),
17709        additional_text_edits: Some(vec![lsp::TextEdit {
17710            range: lsp::Range {
17711                start: lsp::Position {
17712                    line: 0,
17713                    character: 20,
17714                },
17715                end: lsp::Position {
17716                    line: 0,
17717                    character: 22,
17718                },
17719            },
17720            new_text: "".to_string(),
17721        }]),
17722        ..Default::default()
17723    };
17724
17725    let closure_completion_item = completion_item.clone();
17726    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17727        let task_completion_item = closure_completion_item.clone();
17728        async move {
17729            Ok(Some(lsp::CompletionResponse::Array(vec![
17730                task_completion_item,
17731            ])))
17732        }
17733    });
17734
17735    request.next().await;
17736
17737    cx.condition(|editor, _| editor.context_menu_visible())
17738        .await;
17739    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
17740        editor
17741            .confirm_completion(&ConfirmCompletion::default(), window, cx)
17742            .unwrap()
17743    });
17744    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
17745
17746    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
17747        let task_completion_item = completion_item.clone();
17748        async move { Ok(task_completion_item) }
17749    })
17750    .next()
17751    .await
17752    .unwrap();
17753    apply_additional_edits.await.unwrap();
17754    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
17755}
17756
17757#[gpui::test]
17758async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
17759    init_test(cx, |_| {});
17760
17761    let mut cx = EditorLspTestContext::new_rust(
17762        lsp::ServerCapabilities {
17763            completion_provider: Some(lsp::CompletionOptions {
17764                trigger_characters: Some(vec![".".to_string()]),
17765                resolve_provider: Some(true),
17766                ..Default::default()
17767            }),
17768            ..Default::default()
17769        },
17770        cx,
17771    )
17772    .await;
17773
17774    cx.set_state("fn main() { let a = 2ˇ; }");
17775    cx.simulate_keystroke(".");
17776
17777    let item1 = lsp::CompletionItem {
17778        label: "method id()".to_string(),
17779        filter_text: Some("id".to_string()),
17780        detail: None,
17781        documentation: None,
17782        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17783            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17784            new_text: ".id".to_string(),
17785        })),
17786        ..lsp::CompletionItem::default()
17787    };
17788
17789    let item2 = lsp::CompletionItem {
17790        label: "other".to_string(),
17791        filter_text: Some("other".to_string()),
17792        detail: None,
17793        documentation: None,
17794        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17795            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17796            new_text: ".other".to_string(),
17797        })),
17798        ..lsp::CompletionItem::default()
17799    };
17800
17801    let item1 = item1.clone();
17802    cx.set_request_handler::<lsp::request::Completion, _, _>({
17803        let item1 = item1.clone();
17804        move |_, _, _| {
17805            let item1 = item1.clone();
17806            let item2 = item2.clone();
17807            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
17808        }
17809    })
17810    .next()
17811    .await;
17812
17813    cx.condition(|editor, _| editor.context_menu_visible())
17814        .await;
17815    cx.update_editor(|editor, _, _| {
17816        let context_menu = editor.context_menu.borrow_mut();
17817        let context_menu = context_menu
17818            .as_ref()
17819            .expect("Should have the context menu deployed");
17820        match context_menu {
17821            CodeContextMenu::Completions(completions_menu) => {
17822                let completions = completions_menu.completions.borrow_mut();
17823                assert_eq!(
17824                    completions
17825                        .iter()
17826                        .map(|completion| &completion.label.text)
17827                        .collect::<Vec<_>>(),
17828                    vec!["method id()", "other"]
17829                )
17830            }
17831            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17832        }
17833    });
17834
17835    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
17836        let item1 = item1.clone();
17837        move |_, item_to_resolve, _| {
17838            let item1 = item1.clone();
17839            async move {
17840                if item1 == item_to_resolve {
17841                    Ok(lsp::CompletionItem {
17842                        label: "method id()".to_string(),
17843                        filter_text: Some("id".to_string()),
17844                        detail: Some("Now resolved!".to_string()),
17845                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
17846                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17847                            range: lsp::Range::new(
17848                                lsp::Position::new(0, 22),
17849                                lsp::Position::new(0, 22),
17850                            ),
17851                            new_text: ".id".to_string(),
17852                        })),
17853                        ..lsp::CompletionItem::default()
17854                    })
17855                } else {
17856                    Ok(item_to_resolve)
17857                }
17858            }
17859        }
17860    })
17861    .next()
17862    .await
17863    .unwrap();
17864    cx.run_until_parked();
17865
17866    cx.update_editor(|editor, window, cx| {
17867        editor.context_menu_next(&Default::default(), window, cx);
17868    });
17869
17870    cx.update_editor(|editor, _, _| {
17871        let context_menu = editor.context_menu.borrow_mut();
17872        let context_menu = context_menu
17873            .as_ref()
17874            .expect("Should have the context menu deployed");
17875        match context_menu {
17876            CodeContextMenu::Completions(completions_menu) => {
17877                let completions = completions_menu.completions.borrow_mut();
17878                assert_eq!(
17879                    completions
17880                        .iter()
17881                        .map(|completion| &completion.label.text)
17882                        .collect::<Vec<_>>(),
17883                    vec!["method id() Now resolved!", "other"],
17884                    "Should update first completion label, but not second as the filter text did not match."
17885                );
17886            }
17887            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17888        }
17889    });
17890}
17891
17892#[gpui::test]
17893async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
17894    init_test(cx, |_| {});
17895    let mut cx = EditorLspTestContext::new_rust(
17896        lsp::ServerCapabilities {
17897            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
17898            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
17899            completion_provider: Some(lsp::CompletionOptions {
17900                resolve_provider: Some(true),
17901                ..Default::default()
17902            }),
17903            ..Default::default()
17904        },
17905        cx,
17906    )
17907    .await;
17908    cx.set_state(indoc! {"
17909        struct TestStruct {
17910            field: i32
17911        }
17912
17913        fn mainˇ() {
17914            let unused_var = 42;
17915            let test_struct = TestStruct { field: 42 };
17916        }
17917    "});
17918    let symbol_range = cx.lsp_range(indoc! {"
17919        struct TestStruct {
17920            field: i32
17921        }
17922
17923        «fn main»() {
17924            let unused_var = 42;
17925            let test_struct = TestStruct { field: 42 };
17926        }
17927    "});
17928    let mut hover_requests =
17929        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
17930            Ok(Some(lsp::Hover {
17931                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
17932                    kind: lsp::MarkupKind::Markdown,
17933                    value: "Function documentation".to_string(),
17934                }),
17935                range: Some(symbol_range),
17936            }))
17937        });
17938
17939    // Case 1: Test that code action menu hide hover popover
17940    cx.dispatch_action(Hover);
17941    hover_requests.next().await;
17942    cx.condition(|editor, _| editor.hover_state.visible()).await;
17943    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
17944        move |_, _, _| async move {
17945            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
17946                lsp::CodeAction {
17947                    title: "Remove unused variable".to_string(),
17948                    kind: Some(CodeActionKind::QUICKFIX),
17949                    edit: Some(lsp::WorkspaceEdit {
17950                        changes: Some(
17951                            [(
17952                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
17953                                vec![lsp::TextEdit {
17954                                    range: lsp::Range::new(
17955                                        lsp::Position::new(5, 4),
17956                                        lsp::Position::new(5, 27),
17957                                    ),
17958                                    new_text: "".to_string(),
17959                                }],
17960                            )]
17961                            .into_iter()
17962                            .collect(),
17963                        ),
17964                        ..Default::default()
17965                    }),
17966                    ..Default::default()
17967                },
17968            )]))
17969        },
17970    );
17971    cx.update_editor(|editor, window, cx| {
17972        editor.toggle_code_actions(
17973            &ToggleCodeActions {
17974                deployed_from: None,
17975                quick_launch: false,
17976            },
17977            window,
17978            cx,
17979        );
17980    });
17981    code_action_requests.next().await;
17982    cx.run_until_parked();
17983    cx.condition(|editor, _| editor.context_menu_visible())
17984        .await;
17985    cx.update_editor(|editor, _, _| {
17986        assert!(
17987            !editor.hover_state.visible(),
17988            "Hover popover should be hidden when code action menu is shown"
17989        );
17990        // Hide code actions
17991        editor.context_menu.take();
17992    });
17993
17994    // Case 2: Test that code completions hide hover popover
17995    cx.dispatch_action(Hover);
17996    hover_requests.next().await;
17997    cx.condition(|editor, _| editor.hover_state.visible()).await;
17998    let counter = Arc::new(AtomicUsize::new(0));
17999    let mut completion_requests =
18000        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18001            let counter = counter.clone();
18002            async move {
18003                counter.fetch_add(1, atomic::Ordering::Release);
18004                Ok(Some(lsp::CompletionResponse::Array(vec![
18005                    lsp::CompletionItem {
18006                        label: "main".into(),
18007                        kind: Some(lsp::CompletionItemKind::FUNCTION),
18008                        detail: Some("() -> ()".to_string()),
18009                        ..Default::default()
18010                    },
18011                    lsp::CompletionItem {
18012                        label: "TestStruct".into(),
18013                        kind: Some(lsp::CompletionItemKind::STRUCT),
18014                        detail: Some("struct TestStruct".to_string()),
18015                        ..Default::default()
18016                    },
18017                ])))
18018            }
18019        });
18020    cx.update_editor(|editor, window, cx| {
18021        editor.show_completions(&ShowCompletions, window, cx);
18022    });
18023    completion_requests.next().await;
18024    cx.condition(|editor, _| editor.context_menu_visible())
18025        .await;
18026    cx.update_editor(|editor, _, _| {
18027        assert!(
18028            !editor.hover_state.visible(),
18029            "Hover popover should be hidden when completion menu is shown"
18030        );
18031    });
18032}
18033
18034#[gpui::test]
18035async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
18036    init_test(cx, |_| {});
18037
18038    let mut cx = EditorLspTestContext::new_rust(
18039        lsp::ServerCapabilities {
18040            completion_provider: Some(lsp::CompletionOptions {
18041                trigger_characters: Some(vec![".".to_string()]),
18042                resolve_provider: Some(true),
18043                ..Default::default()
18044            }),
18045            ..Default::default()
18046        },
18047        cx,
18048    )
18049    .await;
18050
18051    cx.set_state("fn main() { let a = 2ˇ; }");
18052    cx.simulate_keystroke(".");
18053
18054    let unresolved_item_1 = lsp::CompletionItem {
18055        label: "id".to_string(),
18056        filter_text: Some("id".to_string()),
18057        detail: None,
18058        documentation: None,
18059        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18060            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18061            new_text: ".id".to_string(),
18062        })),
18063        ..lsp::CompletionItem::default()
18064    };
18065    let resolved_item_1 = lsp::CompletionItem {
18066        additional_text_edits: Some(vec![lsp::TextEdit {
18067            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
18068            new_text: "!!".to_string(),
18069        }]),
18070        ..unresolved_item_1.clone()
18071    };
18072    let unresolved_item_2 = lsp::CompletionItem {
18073        label: "other".to_string(),
18074        filter_text: Some("other".to_string()),
18075        detail: None,
18076        documentation: None,
18077        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18078            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18079            new_text: ".other".to_string(),
18080        })),
18081        ..lsp::CompletionItem::default()
18082    };
18083    let resolved_item_2 = lsp::CompletionItem {
18084        additional_text_edits: Some(vec![lsp::TextEdit {
18085            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
18086            new_text: "??".to_string(),
18087        }]),
18088        ..unresolved_item_2.clone()
18089    };
18090
18091    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
18092    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
18093    cx.lsp
18094        .server
18095        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
18096            let unresolved_item_1 = unresolved_item_1.clone();
18097            let resolved_item_1 = resolved_item_1.clone();
18098            let unresolved_item_2 = unresolved_item_2.clone();
18099            let resolved_item_2 = resolved_item_2.clone();
18100            let resolve_requests_1 = resolve_requests_1.clone();
18101            let resolve_requests_2 = resolve_requests_2.clone();
18102            move |unresolved_request, _| {
18103                let unresolved_item_1 = unresolved_item_1.clone();
18104                let resolved_item_1 = resolved_item_1.clone();
18105                let unresolved_item_2 = unresolved_item_2.clone();
18106                let resolved_item_2 = resolved_item_2.clone();
18107                let resolve_requests_1 = resolve_requests_1.clone();
18108                let resolve_requests_2 = resolve_requests_2.clone();
18109                async move {
18110                    if unresolved_request == unresolved_item_1 {
18111                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
18112                        Ok(resolved_item_1.clone())
18113                    } else if unresolved_request == unresolved_item_2 {
18114                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
18115                        Ok(resolved_item_2.clone())
18116                    } else {
18117                        panic!("Unexpected completion item {unresolved_request:?}")
18118                    }
18119                }
18120            }
18121        })
18122        .detach();
18123
18124    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18125        let unresolved_item_1 = unresolved_item_1.clone();
18126        let unresolved_item_2 = unresolved_item_2.clone();
18127        async move {
18128            Ok(Some(lsp::CompletionResponse::Array(vec![
18129                unresolved_item_1,
18130                unresolved_item_2,
18131            ])))
18132        }
18133    })
18134    .next()
18135    .await;
18136
18137    cx.condition(|editor, _| editor.context_menu_visible())
18138        .await;
18139    cx.update_editor(|editor, _, _| {
18140        let context_menu = editor.context_menu.borrow_mut();
18141        let context_menu = context_menu
18142            .as_ref()
18143            .expect("Should have the context menu deployed");
18144        match context_menu {
18145            CodeContextMenu::Completions(completions_menu) => {
18146                let completions = completions_menu.completions.borrow_mut();
18147                assert_eq!(
18148                    completions
18149                        .iter()
18150                        .map(|completion| &completion.label.text)
18151                        .collect::<Vec<_>>(),
18152                    vec!["id", "other"]
18153                )
18154            }
18155            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18156        }
18157    });
18158    cx.run_until_parked();
18159
18160    cx.update_editor(|editor, window, cx| {
18161        editor.context_menu_next(&ContextMenuNext, window, cx);
18162    });
18163    cx.run_until_parked();
18164    cx.update_editor(|editor, window, cx| {
18165        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
18166    });
18167    cx.run_until_parked();
18168    cx.update_editor(|editor, window, cx| {
18169        editor.context_menu_next(&ContextMenuNext, window, cx);
18170    });
18171    cx.run_until_parked();
18172    cx.update_editor(|editor, window, cx| {
18173        editor
18174            .compose_completion(&ComposeCompletion::default(), window, cx)
18175            .expect("No task returned")
18176    })
18177    .await
18178    .expect("Completion failed");
18179    cx.run_until_parked();
18180
18181    cx.update_editor(|editor, _, cx| {
18182        assert_eq!(
18183            resolve_requests_1.load(atomic::Ordering::Acquire),
18184            1,
18185            "Should always resolve once despite multiple selections"
18186        );
18187        assert_eq!(
18188            resolve_requests_2.load(atomic::Ordering::Acquire),
18189            1,
18190            "Should always resolve once after multiple selections and applying the completion"
18191        );
18192        assert_eq!(
18193            editor.text(cx),
18194            "fn main() { let a = ??.other; }",
18195            "Should use resolved data when applying the completion"
18196        );
18197    });
18198}
18199
18200#[gpui::test]
18201async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
18202    init_test(cx, |_| {});
18203
18204    let item_0 = lsp::CompletionItem {
18205        label: "abs".into(),
18206        insert_text: Some("abs".into()),
18207        data: Some(json!({ "very": "special"})),
18208        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
18209        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
18210            lsp::InsertReplaceEdit {
18211                new_text: "abs".to_string(),
18212                insert: lsp::Range::default(),
18213                replace: lsp::Range::default(),
18214            },
18215        )),
18216        ..lsp::CompletionItem::default()
18217    };
18218    let items = iter::once(item_0.clone())
18219        .chain((11..51).map(|i| lsp::CompletionItem {
18220            label: format!("item_{}", i),
18221            insert_text: Some(format!("item_{}", i)),
18222            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
18223            ..lsp::CompletionItem::default()
18224        }))
18225        .collect::<Vec<_>>();
18226
18227    let default_commit_characters = vec!["?".to_string()];
18228    let default_data = json!({ "default": "data"});
18229    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
18230    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
18231    let default_edit_range = lsp::Range {
18232        start: lsp::Position {
18233            line: 0,
18234            character: 5,
18235        },
18236        end: lsp::Position {
18237            line: 0,
18238            character: 5,
18239        },
18240    };
18241
18242    let mut cx = EditorLspTestContext::new_rust(
18243        lsp::ServerCapabilities {
18244            completion_provider: Some(lsp::CompletionOptions {
18245                trigger_characters: Some(vec![".".to_string()]),
18246                resolve_provider: Some(true),
18247                ..Default::default()
18248            }),
18249            ..Default::default()
18250        },
18251        cx,
18252    )
18253    .await;
18254
18255    cx.set_state("fn main() { let a = 2ˇ; }");
18256    cx.simulate_keystroke(".");
18257
18258    let completion_data = default_data.clone();
18259    let completion_characters = default_commit_characters.clone();
18260    let completion_items = items.clone();
18261    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18262        let default_data = completion_data.clone();
18263        let default_commit_characters = completion_characters.clone();
18264        let items = completion_items.clone();
18265        async move {
18266            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
18267                items,
18268                item_defaults: Some(lsp::CompletionListItemDefaults {
18269                    data: Some(default_data.clone()),
18270                    commit_characters: Some(default_commit_characters.clone()),
18271                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
18272                        default_edit_range,
18273                    )),
18274                    insert_text_format: Some(default_insert_text_format),
18275                    insert_text_mode: Some(default_insert_text_mode),
18276                }),
18277                ..lsp::CompletionList::default()
18278            })))
18279        }
18280    })
18281    .next()
18282    .await;
18283
18284    let resolved_items = Arc::new(Mutex::new(Vec::new()));
18285    cx.lsp
18286        .server
18287        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
18288            let closure_resolved_items = resolved_items.clone();
18289            move |item_to_resolve, _| {
18290                let closure_resolved_items = closure_resolved_items.clone();
18291                async move {
18292                    closure_resolved_items.lock().push(item_to_resolve.clone());
18293                    Ok(item_to_resolve)
18294                }
18295            }
18296        })
18297        .detach();
18298
18299    cx.condition(|editor, _| editor.context_menu_visible())
18300        .await;
18301    cx.run_until_parked();
18302    cx.update_editor(|editor, _, _| {
18303        let menu = editor.context_menu.borrow_mut();
18304        match menu.as_ref().expect("should have the completions menu") {
18305            CodeContextMenu::Completions(completions_menu) => {
18306                assert_eq!(
18307                    completions_menu
18308                        .entries
18309                        .borrow()
18310                        .iter()
18311                        .map(|mat| mat.string.clone())
18312                        .collect::<Vec<String>>(),
18313                    items
18314                        .iter()
18315                        .map(|completion| completion.label.clone())
18316                        .collect::<Vec<String>>()
18317                );
18318            }
18319            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
18320        }
18321    });
18322    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
18323    // with 4 from the end.
18324    assert_eq!(
18325        *resolved_items.lock(),
18326        [&items[0..16], &items[items.len() - 4..items.len()]]
18327            .concat()
18328            .iter()
18329            .cloned()
18330            .map(|mut item| {
18331                if item.data.is_none() {
18332                    item.data = Some(default_data.clone());
18333                }
18334                item
18335            })
18336            .collect::<Vec<lsp::CompletionItem>>(),
18337        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
18338    );
18339    resolved_items.lock().clear();
18340
18341    cx.update_editor(|editor, window, cx| {
18342        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
18343    });
18344    cx.run_until_parked();
18345    // Completions that have already been resolved are skipped.
18346    assert_eq!(
18347        *resolved_items.lock(),
18348        items[items.len() - 17..items.len() - 4]
18349            .iter()
18350            .cloned()
18351            .map(|mut item| {
18352                if item.data.is_none() {
18353                    item.data = Some(default_data.clone());
18354                }
18355                item
18356            })
18357            .collect::<Vec<lsp::CompletionItem>>()
18358    );
18359    resolved_items.lock().clear();
18360}
18361
18362#[gpui::test]
18363async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
18364    init_test(cx, |_| {});
18365
18366    let mut cx = EditorLspTestContext::new(
18367        Language::new(
18368            LanguageConfig {
18369                matcher: LanguageMatcher {
18370                    path_suffixes: vec!["jsx".into()],
18371                    ..Default::default()
18372                },
18373                overrides: [(
18374                    "element".into(),
18375                    LanguageConfigOverride {
18376                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
18377                        ..Default::default()
18378                    },
18379                )]
18380                .into_iter()
18381                .collect(),
18382                ..Default::default()
18383            },
18384            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
18385        )
18386        .with_override_query("(jsx_self_closing_element) @element")
18387        .unwrap(),
18388        lsp::ServerCapabilities {
18389            completion_provider: Some(lsp::CompletionOptions {
18390                trigger_characters: Some(vec![":".to_string()]),
18391                ..Default::default()
18392            }),
18393            ..Default::default()
18394        },
18395        cx,
18396    )
18397    .await;
18398
18399    cx.lsp
18400        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
18401            Ok(Some(lsp::CompletionResponse::Array(vec![
18402                lsp::CompletionItem {
18403                    label: "bg-blue".into(),
18404                    ..Default::default()
18405                },
18406                lsp::CompletionItem {
18407                    label: "bg-red".into(),
18408                    ..Default::default()
18409                },
18410                lsp::CompletionItem {
18411                    label: "bg-yellow".into(),
18412                    ..Default::default()
18413                },
18414            ])))
18415        });
18416
18417    cx.set_state(r#"<p class="bgˇ" />"#);
18418
18419    // Trigger completion when typing a dash, because the dash is an extra
18420    // word character in the 'element' scope, which contains the cursor.
18421    cx.simulate_keystroke("-");
18422    cx.executor().run_until_parked();
18423    cx.update_editor(|editor, _, _| {
18424        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18425        {
18426            assert_eq!(
18427                completion_menu_entries(menu),
18428                &["bg-blue", "bg-red", "bg-yellow"]
18429            );
18430        } else {
18431            panic!("expected completion menu to be open");
18432        }
18433    });
18434
18435    cx.simulate_keystroke("l");
18436    cx.executor().run_until_parked();
18437    cx.update_editor(|editor, _, _| {
18438        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18439        {
18440            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
18441        } else {
18442            panic!("expected completion menu to be open");
18443        }
18444    });
18445
18446    // When filtering completions, consider the character after the '-' to
18447    // be the start of a subword.
18448    cx.set_state(r#"<p class="yelˇ" />"#);
18449    cx.simulate_keystroke("l");
18450    cx.executor().run_until_parked();
18451    cx.update_editor(|editor, _, _| {
18452        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18453        {
18454            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
18455        } else {
18456            panic!("expected completion menu to be open");
18457        }
18458    });
18459}
18460
18461fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
18462    let entries = menu.entries.borrow();
18463    entries.iter().map(|mat| mat.string.clone()).collect()
18464}
18465
18466#[gpui::test]
18467async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
18468    init_test(cx, |settings| {
18469        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
18470    });
18471
18472    let fs = FakeFs::new(cx.executor());
18473    fs.insert_file(path!("/file.ts"), Default::default()).await;
18474
18475    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
18476    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18477
18478    language_registry.add(Arc::new(Language::new(
18479        LanguageConfig {
18480            name: "TypeScript".into(),
18481            matcher: LanguageMatcher {
18482                path_suffixes: vec!["ts".to_string()],
18483                ..Default::default()
18484            },
18485            ..Default::default()
18486        },
18487        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
18488    )));
18489    update_test_language_settings(cx, |settings| {
18490        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
18491    });
18492
18493    let test_plugin = "test_plugin";
18494    let _ = language_registry.register_fake_lsp(
18495        "TypeScript",
18496        FakeLspAdapter {
18497            prettier_plugins: vec![test_plugin],
18498            ..Default::default()
18499        },
18500    );
18501
18502    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
18503    let buffer = project
18504        .update(cx, |project, cx| {
18505            project.open_local_buffer(path!("/file.ts"), cx)
18506        })
18507        .await
18508        .unwrap();
18509
18510    let buffer_text = "one\ntwo\nthree\n";
18511    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
18512    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
18513    editor.update_in(cx, |editor, window, cx| {
18514        editor.set_text(buffer_text, window, cx)
18515    });
18516
18517    editor
18518        .update_in(cx, |editor, window, cx| {
18519            editor.perform_format(
18520                project.clone(),
18521                FormatTrigger::Manual,
18522                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18523                window,
18524                cx,
18525            )
18526        })
18527        .unwrap()
18528        .await;
18529    assert_eq!(
18530        editor.update(cx, |editor, cx| editor.text(cx)),
18531        buffer_text.to_string() + prettier_format_suffix,
18532        "Test prettier formatting was not applied to the original buffer text",
18533    );
18534
18535    update_test_language_settings(cx, |settings| {
18536        settings.defaults.formatter = Some(FormatterList::default())
18537    });
18538    let format = editor.update_in(cx, |editor, window, cx| {
18539        editor.perform_format(
18540            project.clone(),
18541            FormatTrigger::Manual,
18542            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18543            window,
18544            cx,
18545        )
18546    });
18547    format.await.unwrap();
18548    assert_eq!(
18549        editor.update(cx, |editor, cx| editor.text(cx)),
18550        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
18551        "Autoformatting (via test prettier) was not applied to the original buffer text",
18552    );
18553}
18554
18555#[gpui::test]
18556async fn test_addition_reverts(cx: &mut TestAppContext) {
18557    init_test(cx, |_| {});
18558    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18559    let base_text = indoc! {r#"
18560        struct Row;
18561        struct Row1;
18562        struct Row2;
18563
18564        struct Row4;
18565        struct Row5;
18566        struct Row6;
18567
18568        struct Row8;
18569        struct Row9;
18570        struct Row10;"#};
18571
18572    // When addition hunks are not adjacent to carets, no hunk revert is performed
18573    assert_hunk_revert(
18574        indoc! {r#"struct Row;
18575                   struct Row1;
18576                   struct Row1.1;
18577                   struct Row1.2;
18578                   struct Row2;ˇ
18579
18580                   struct Row4;
18581                   struct Row5;
18582                   struct Row6;
18583
18584                   struct Row8;
18585                   ˇstruct Row9;
18586                   struct Row9.1;
18587                   struct Row9.2;
18588                   struct Row9.3;
18589                   struct Row10;"#},
18590        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18591        indoc! {r#"struct Row;
18592                   struct Row1;
18593                   struct Row1.1;
18594                   struct Row1.2;
18595                   struct Row2;ˇ
18596
18597                   struct Row4;
18598                   struct Row5;
18599                   struct Row6;
18600
18601                   struct Row8;
18602                   ˇstruct Row9;
18603                   struct Row9.1;
18604                   struct Row9.2;
18605                   struct Row9.3;
18606                   struct Row10;"#},
18607        base_text,
18608        &mut cx,
18609    );
18610    // Same for selections
18611    assert_hunk_revert(
18612        indoc! {r#"struct Row;
18613                   struct Row1;
18614                   struct Row2;
18615                   struct Row2.1;
18616                   struct Row2.2;
18617                   «ˇ
18618                   struct Row4;
18619                   struct» Row5;
18620                   «struct Row6;
18621                   ˇ»
18622                   struct Row9.1;
18623                   struct Row9.2;
18624                   struct Row9.3;
18625                   struct Row8;
18626                   struct Row9;
18627                   struct Row10;"#},
18628        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18629        indoc! {r#"struct Row;
18630                   struct Row1;
18631                   struct Row2;
18632                   struct Row2.1;
18633                   struct Row2.2;
18634                   «ˇ
18635                   struct Row4;
18636                   struct» Row5;
18637                   «struct Row6;
18638                   ˇ»
18639                   struct Row9.1;
18640                   struct Row9.2;
18641                   struct Row9.3;
18642                   struct Row8;
18643                   struct Row9;
18644                   struct Row10;"#},
18645        base_text,
18646        &mut cx,
18647    );
18648
18649    // When carets and selections intersect the addition hunks, those are reverted.
18650    // Adjacent carets got merged.
18651    assert_hunk_revert(
18652        indoc! {r#"struct Row;
18653                   ˇ// something on the top
18654                   struct Row1;
18655                   struct Row2;
18656                   struct Roˇw3.1;
18657                   struct Row2.2;
18658                   struct Row2.3;ˇ
18659
18660                   struct Row4;
18661                   struct ˇRow5.1;
18662                   struct Row5.2;
18663                   struct «Rowˇ»5.3;
18664                   struct Row5;
18665                   struct Row6;
18666                   ˇ
18667                   struct Row9.1;
18668                   struct «Rowˇ»9.2;
18669                   struct «ˇRow»9.3;
18670                   struct Row8;
18671                   struct Row9;
18672                   «ˇ// something on bottom»
18673                   struct Row10;"#},
18674        vec![
18675            DiffHunkStatusKind::Added,
18676            DiffHunkStatusKind::Added,
18677            DiffHunkStatusKind::Added,
18678            DiffHunkStatusKind::Added,
18679            DiffHunkStatusKind::Added,
18680        ],
18681        indoc! {r#"struct Row;
18682                   ˇstruct Row1;
18683                   struct Row2;
18684                   ˇ
18685                   struct Row4;
18686                   ˇstruct Row5;
18687                   struct Row6;
18688                   ˇ
18689                   ˇstruct Row8;
18690                   struct Row9;
18691                   ˇstruct Row10;"#},
18692        base_text,
18693        &mut cx,
18694    );
18695}
18696
18697#[gpui::test]
18698async fn test_modification_reverts(cx: &mut TestAppContext) {
18699    init_test(cx, |_| {});
18700    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18701    let base_text = indoc! {r#"
18702        struct Row;
18703        struct Row1;
18704        struct Row2;
18705
18706        struct Row4;
18707        struct Row5;
18708        struct Row6;
18709
18710        struct Row8;
18711        struct Row9;
18712        struct Row10;"#};
18713
18714    // Modification hunks behave the same as the addition ones.
18715    assert_hunk_revert(
18716        indoc! {r#"struct Row;
18717                   struct Row1;
18718                   struct Row33;
18719                   ˇ
18720                   struct Row4;
18721                   struct Row5;
18722                   struct Row6;
18723                   ˇ
18724                   struct Row99;
18725                   struct Row9;
18726                   struct Row10;"#},
18727        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18728        indoc! {r#"struct Row;
18729                   struct Row1;
18730                   struct Row33;
18731                   ˇ
18732                   struct Row4;
18733                   struct Row5;
18734                   struct Row6;
18735                   ˇ
18736                   struct Row99;
18737                   struct Row9;
18738                   struct Row10;"#},
18739        base_text,
18740        &mut cx,
18741    );
18742    assert_hunk_revert(
18743        indoc! {r#"struct Row;
18744                   struct Row1;
18745                   struct Row33;
18746                   «ˇ
18747                   struct Row4;
18748                   struct» Row5;
18749                   «struct Row6;
18750                   ˇ»
18751                   struct Row99;
18752                   struct Row9;
18753                   struct Row10;"#},
18754        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18755        indoc! {r#"struct Row;
18756                   struct Row1;
18757                   struct Row33;
18758                   «ˇ
18759                   struct Row4;
18760                   struct» Row5;
18761                   «struct Row6;
18762                   ˇ»
18763                   struct Row99;
18764                   struct Row9;
18765                   struct Row10;"#},
18766        base_text,
18767        &mut cx,
18768    );
18769
18770    assert_hunk_revert(
18771        indoc! {r#"ˇstruct Row1.1;
18772                   struct Row1;
18773                   «ˇstr»uct Row22;
18774
18775                   struct ˇRow44;
18776                   struct Row5;
18777                   struct «Rˇ»ow66;ˇ
18778
18779                   «struˇ»ct Row88;
18780                   struct Row9;
18781                   struct Row1011;ˇ"#},
18782        vec![
18783            DiffHunkStatusKind::Modified,
18784            DiffHunkStatusKind::Modified,
18785            DiffHunkStatusKind::Modified,
18786            DiffHunkStatusKind::Modified,
18787            DiffHunkStatusKind::Modified,
18788            DiffHunkStatusKind::Modified,
18789        ],
18790        indoc! {r#"struct Row;
18791                   ˇstruct Row1;
18792                   struct Row2;
18793                   ˇ
18794                   struct Row4;
18795                   ˇstruct Row5;
18796                   struct Row6;
18797                   ˇ
18798                   struct Row8;
18799                   ˇstruct Row9;
18800                   struct Row10;ˇ"#},
18801        base_text,
18802        &mut cx,
18803    );
18804}
18805
18806#[gpui::test]
18807async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
18808    init_test(cx, |_| {});
18809    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18810    let base_text = indoc! {r#"
18811        one
18812
18813        two
18814        three
18815        "#};
18816
18817    cx.set_head_text(base_text);
18818    cx.set_state("\nˇ\n");
18819    cx.executor().run_until_parked();
18820    cx.update_editor(|editor, _window, cx| {
18821        editor.expand_selected_diff_hunks(cx);
18822    });
18823    cx.executor().run_until_parked();
18824    cx.update_editor(|editor, window, cx| {
18825        editor.backspace(&Default::default(), window, cx);
18826    });
18827    cx.run_until_parked();
18828    cx.assert_state_with_diff(
18829        indoc! {r#"
18830
18831        - two
18832        - threeˇ
18833        +
18834        "#}
18835        .to_string(),
18836    );
18837}
18838
18839#[gpui::test]
18840async fn test_deletion_reverts(cx: &mut TestAppContext) {
18841    init_test(cx, |_| {});
18842    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18843    let base_text = indoc! {r#"struct Row;
18844struct Row1;
18845struct Row2;
18846
18847struct Row4;
18848struct Row5;
18849struct Row6;
18850
18851struct Row8;
18852struct Row9;
18853struct Row10;"#};
18854
18855    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
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    assert_hunk_revert(
18880        indoc! {r#"struct Row;
18881                   struct Row2;
18882
18883                   «ˇstruct Row4;
18884                   struct» Row5;
18885                   «struct Row6;
18886                   ˇ»
18887                   struct Row8;
18888                   struct Row10;"#},
18889        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18890        indoc! {r#"struct Row;
18891                   struct Row2;
18892
18893                   «ˇstruct Row4;
18894                   struct» Row5;
18895                   «struct Row6;
18896                   ˇ»
18897                   struct Row8;
18898                   struct Row10;"#},
18899        base_text,
18900        &mut cx,
18901    );
18902
18903    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
18904    assert_hunk_revert(
18905        indoc! {r#"struct Row;
18906                   ˇstruct Row2;
18907
18908                   struct Row4;
18909                   struct Row5;
18910                   struct Row6;
18911
18912                   struct Row8;ˇ
18913                   struct Row10;"#},
18914        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18915        indoc! {r#"struct Row;
18916                   struct Row1;
18917                   ˇstruct Row2;
18918
18919                   struct Row4;
18920                   struct Row5;
18921                   struct Row6;
18922
18923                   struct Row8;ˇ
18924                   struct Row9;
18925                   struct Row10;"#},
18926        base_text,
18927        &mut cx,
18928    );
18929    assert_hunk_revert(
18930        indoc! {r#"struct Row;
18931                   struct Row2«ˇ;
18932                   struct Row4;
18933                   struct» Row5;
18934                   «struct Row6;
18935
18936                   struct Row8;ˇ»
18937                   struct Row10;"#},
18938        vec![
18939            DiffHunkStatusKind::Deleted,
18940            DiffHunkStatusKind::Deleted,
18941            DiffHunkStatusKind::Deleted,
18942        ],
18943        indoc! {r#"struct Row;
18944                   struct Row1;
18945                   struct Row2«ˇ;
18946
18947                   struct Row4;
18948                   struct» Row5;
18949                   «struct Row6;
18950
18951                   struct Row8;ˇ»
18952                   struct Row9;
18953                   struct Row10;"#},
18954        base_text,
18955        &mut cx,
18956    );
18957}
18958
18959#[gpui::test]
18960async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
18961    init_test(cx, |_| {});
18962
18963    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
18964    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
18965    let base_text_3 =
18966        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
18967
18968    let text_1 = edit_first_char_of_every_line(base_text_1);
18969    let text_2 = edit_first_char_of_every_line(base_text_2);
18970    let text_3 = edit_first_char_of_every_line(base_text_3);
18971
18972    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
18973    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
18974    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
18975
18976    let multibuffer = cx.new(|cx| {
18977        let mut multibuffer = MultiBuffer::new(ReadWrite);
18978        multibuffer.push_excerpts(
18979            buffer_1.clone(),
18980            [
18981                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18982                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18983                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18984            ],
18985            cx,
18986        );
18987        multibuffer.push_excerpts(
18988            buffer_2.clone(),
18989            [
18990                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18991                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18992                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18993            ],
18994            cx,
18995        );
18996        multibuffer.push_excerpts(
18997            buffer_3.clone(),
18998            [
18999                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19000                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19001                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19002            ],
19003            cx,
19004        );
19005        multibuffer
19006    });
19007
19008    let fs = FakeFs::new(cx.executor());
19009    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
19010    let (editor, cx) = cx
19011        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
19012    editor.update_in(cx, |editor, _window, cx| {
19013        for (buffer, diff_base) in [
19014            (buffer_1.clone(), base_text_1),
19015            (buffer_2.clone(), base_text_2),
19016            (buffer_3.clone(), base_text_3),
19017        ] {
19018            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
19019            editor
19020                .buffer
19021                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
19022        }
19023    });
19024    cx.executor().run_until_parked();
19025
19026    editor.update_in(cx, |editor, window, cx| {
19027        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}");
19028        editor.select_all(&SelectAll, window, cx);
19029        editor.git_restore(&Default::default(), window, cx);
19030    });
19031    cx.executor().run_until_parked();
19032
19033    // When all ranges are selected, all buffer hunks are reverted.
19034    editor.update(cx, |editor, cx| {
19035        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");
19036    });
19037    buffer_1.update(cx, |buffer, _| {
19038        assert_eq!(buffer.text(), base_text_1);
19039    });
19040    buffer_2.update(cx, |buffer, _| {
19041        assert_eq!(buffer.text(), base_text_2);
19042    });
19043    buffer_3.update(cx, |buffer, _| {
19044        assert_eq!(buffer.text(), base_text_3);
19045    });
19046
19047    editor.update_in(cx, |editor, window, cx| {
19048        editor.undo(&Default::default(), window, cx);
19049    });
19050
19051    editor.update_in(cx, |editor, window, cx| {
19052        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19053            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
19054        });
19055        editor.git_restore(&Default::default(), window, cx);
19056    });
19057
19058    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
19059    // but not affect buffer_2 and its related excerpts.
19060    editor.update(cx, |editor, cx| {
19061        assert_eq!(
19062            editor.text(cx),
19063            "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}"
19064        );
19065    });
19066    buffer_1.update(cx, |buffer, _| {
19067        assert_eq!(buffer.text(), base_text_1);
19068    });
19069    buffer_2.update(cx, |buffer, _| {
19070        assert_eq!(
19071            buffer.text(),
19072            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
19073        );
19074    });
19075    buffer_3.update(cx, |buffer, _| {
19076        assert_eq!(
19077            buffer.text(),
19078            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
19079        );
19080    });
19081
19082    fn edit_first_char_of_every_line(text: &str) -> String {
19083        text.split('\n')
19084            .map(|line| format!("X{}", &line[1..]))
19085            .collect::<Vec<_>>()
19086            .join("\n")
19087    }
19088}
19089
19090#[gpui::test]
19091async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
19092    init_test(cx, |_| {});
19093
19094    let cols = 4;
19095    let rows = 10;
19096    let sample_text_1 = sample_text(rows, cols, 'a');
19097    assert_eq!(
19098        sample_text_1,
19099        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
19100    );
19101    let sample_text_2 = sample_text(rows, cols, 'l');
19102    assert_eq!(
19103        sample_text_2,
19104        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
19105    );
19106    let sample_text_3 = sample_text(rows, cols, 'v');
19107    assert_eq!(
19108        sample_text_3,
19109        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
19110    );
19111
19112    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
19113    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
19114    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
19115
19116    let multi_buffer = cx.new(|cx| {
19117        let mut multibuffer = MultiBuffer::new(ReadWrite);
19118        multibuffer.push_excerpts(
19119            buffer_1.clone(),
19120            [
19121                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19122                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19123                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19124            ],
19125            cx,
19126        );
19127        multibuffer.push_excerpts(
19128            buffer_2.clone(),
19129            [
19130                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19131                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19132                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19133            ],
19134            cx,
19135        );
19136        multibuffer.push_excerpts(
19137            buffer_3.clone(),
19138            [
19139                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19140                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19141                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19142            ],
19143            cx,
19144        );
19145        multibuffer
19146    });
19147
19148    let fs = FakeFs::new(cx.executor());
19149    fs.insert_tree(
19150        "/a",
19151        json!({
19152            "main.rs": sample_text_1,
19153            "other.rs": sample_text_2,
19154            "lib.rs": sample_text_3,
19155        }),
19156    )
19157    .await;
19158    let project = Project::test(fs, ["/a".as_ref()], cx).await;
19159    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19160    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19161    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19162        Editor::new(
19163            EditorMode::full(),
19164            multi_buffer,
19165            Some(project.clone()),
19166            window,
19167            cx,
19168        )
19169    });
19170    let multibuffer_item_id = workspace
19171        .update(cx, |workspace, window, cx| {
19172            assert!(
19173                workspace.active_item(cx).is_none(),
19174                "active item should be None before the first item is added"
19175            );
19176            workspace.add_item_to_active_pane(
19177                Box::new(multi_buffer_editor.clone()),
19178                None,
19179                true,
19180                window,
19181                cx,
19182            );
19183            let active_item = workspace
19184                .active_item(cx)
19185                .expect("should have an active item after adding the multi buffer");
19186            assert_eq!(
19187                active_item.buffer_kind(cx),
19188                ItemBufferKind::Multibuffer,
19189                "A multi buffer was expected to active after adding"
19190            );
19191            active_item.item_id()
19192        })
19193        .unwrap();
19194    cx.executor().run_until_parked();
19195
19196    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19197        editor.change_selections(
19198            SelectionEffects::scroll(Autoscroll::Next),
19199            window,
19200            cx,
19201            |s| s.select_ranges(Some(1..2)),
19202        );
19203        editor.open_excerpts(&OpenExcerpts, window, cx);
19204    });
19205    cx.executor().run_until_parked();
19206    let first_item_id = workspace
19207        .update(cx, |workspace, window, cx| {
19208            let active_item = workspace
19209                .active_item(cx)
19210                .expect("should have an active item after navigating into the 1st buffer");
19211            let first_item_id = active_item.item_id();
19212            assert_ne!(
19213                first_item_id, multibuffer_item_id,
19214                "Should navigate into the 1st buffer and activate it"
19215            );
19216            assert_eq!(
19217                active_item.buffer_kind(cx),
19218                ItemBufferKind::Singleton,
19219                "New active item should be a singleton buffer"
19220            );
19221            assert_eq!(
19222                active_item
19223                    .act_as::<Editor>(cx)
19224                    .expect("should have navigated into an editor for the 1st buffer")
19225                    .read(cx)
19226                    .text(cx),
19227                sample_text_1
19228            );
19229
19230            workspace
19231                .go_back(workspace.active_pane().downgrade(), window, cx)
19232                .detach_and_log_err(cx);
19233
19234            first_item_id
19235        })
19236        .unwrap();
19237    cx.executor().run_until_parked();
19238    workspace
19239        .update(cx, |workspace, _, cx| {
19240            let active_item = workspace
19241                .active_item(cx)
19242                .expect("should have an active item after navigating back");
19243            assert_eq!(
19244                active_item.item_id(),
19245                multibuffer_item_id,
19246                "Should navigate back to the multi buffer"
19247            );
19248            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19249        })
19250        .unwrap();
19251
19252    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19253        editor.change_selections(
19254            SelectionEffects::scroll(Autoscroll::Next),
19255            window,
19256            cx,
19257            |s| s.select_ranges(Some(39..40)),
19258        );
19259        editor.open_excerpts(&OpenExcerpts, window, cx);
19260    });
19261    cx.executor().run_until_parked();
19262    let second_item_id = workspace
19263        .update(cx, |workspace, window, cx| {
19264            let active_item = workspace
19265                .active_item(cx)
19266                .expect("should have an active item after navigating into the 2nd buffer");
19267            let second_item_id = active_item.item_id();
19268            assert_ne!(
19269                second_item_id, multibuffer_item_id,
19270                "Should navigate away from the multibuffer"
19271            );
19272            assert_ne!(
19273                second_item_id, first_item_id,
19274                "Should navigate into the 2nd buffer and activate it"
19275            );
19276            assert_eq!(
19277                active_item.buffer_kind(cx),
19278                ItemBufferKind::Singleton,
19279                "New active item should be a singleton buffer"
19280            );
19281            assert_eq!(
19282                active_item
19283                    .act_as::<Editor>(cx)
19284                    .expect("should have navigated into an editor")
19285                    .read(cx)
19286                    .text(cx),
19287                sample_text_2
19288            );
19289
19290            workspace
19291                .go_back(workspace.active_pane().downgrade(), window, cx)
19292                .detach_and_log_err(cx);
19293
19294            second_item_id
19295        })
19296        .unwrap();
19297    cx.executor().run_until_parked();
19298    workspace
19299        .update(cx, |workspace, _, cx| {
19300            let active_item = workspace
19301                .active_item(cx)
19302                .expect("should have an active item after navigating back from the 2nd buffer");
19303            assert_eq!(
19304                active_item.item_id(),
19305                multibuffer_item_id,
19306                "Should navigate back from the 2nd buffer to the multi buffer"
19307            );
19308            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19309        })
19310        .unwrap();
19311
19312    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19313        editor.change_selections(
19314            SelectionEffects::scroll(Autoscroll::Next),
19315            window,
19316            cx,
19317            |s| s.select_ranges(Some(70..70)),
19318        );
19319        editor.open_excerpts(&OpenExcerpts, window, cx);
19320    });
19321    cx.executor().run_until_parked();
19322    workspace
19323        .update(cx, |workspace, window, cx| {
19324            let active_item = workspace
19325                .active_item(cx)
19326                .expect("should have an active item after navigating into the 3rd buffer");
19327            let third_item_id = active_item.item_id();
19328            assert_ne!(
19329                third_item_id, multibuffer_item_id,
19330                "Should navigate into the 3rd buffer and activate it"
19331            );
19332            assert_ne!(third_item_id, first_item_id);
19333            assert_ne!(third_item_id, second_item_id);
19334            assert_eq!(
19335                active_item.buffer_kind(cx),
19336                ItemBufferKind::Singleton,
19337                "New active item should be a singleton buffer"
19338            );
19339            assert_eq!(
19340                active_item
19341                    .act_as::<Editor>(cx)
19342                    .expect("should have navigated into an editor")
19343                    .read(cx)
19344                    .text(cx),
19345                sample_text_3
19346            );
19347
19348            workspace
19349                .go_back(workspace.active_pane().downgrade(), window, cx)
19350                .detach_and_log_err(cx);
19351        })
19352        .unwrap();
19353    cx.executor().run_until_parked();
19354    workspace
19355        .update(cx, |workspace, _, cx| {
19356            let active_item = workspace
19357                .active_item(cx)
19358                .expect("should have an active item after navigating back from the 3rd buffer");
19359            assert_eq!(
19360                active_item.item_id(),
19361                multibuffer_item_id,
19362                "Should navigate back from the 3rd buffer to the multi buffer"
19363            );
19364            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19365        })
19366        .unwrap();
19367}
19368
19369#[gpui::test]
19370async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19371    init_test(cx, |_| {});
19372
19373    let mut cx = EditorTestContext::new(cx).await;
19374
19375    let diff_base = r#"
19376        use some::mod;
19377
19378        const A: u32 = 42;
19379
19380        fn main() {
19381            println!("hello");
19382
19383            println!("world");
19384        }
19385        "#
19386    .unindent();
19387
19388    cx.set_state(
19389        &r#"
19390        use some::modified;
19391
19392        ˇ
19393        fn main() {
19394            println!("hello there");
19395
19396            println!("around the");
19397            println!("world");
19398        }
19399        "#
19400        .unindent(),
19401    );
19402
19403    cx.set_head_text(&diff_base);
19404    executor.run_until_parked();
19405
19406    cx.update_editor(|editor, window, cx| {
19407        editor.go_to_next_hunk(&GoToHunk, window, cx);
19408        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19409    });
19410    executor.run_until_parked();
19411    cx.assert_state_with_diff(
19412        r#"
19413          use some::modified;
19414
19415
19416          fn main() {
19417        -     println!("hello");
19418        + ˇ    println!("hello there");
19419
19420              println!("around the");
19421              println!("world");
19422          }
19423        "#
19424        .unindent(),
19425    );
19426
19427    cx.update_editor(|editor, window, cx| {
19428        for _ in 0..2 {
19429            editor.go_to_next_hunk(&GoToHunk, window, cx);
19430            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19431        }
19432    });
19433    executor.run_until_parked();
19434    cx.assert_state_with_diff(
19435        r#"
19436        - use some::mod;
19437        + ˇuse some::modified;
19438
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.go_to_next_hunk(&GoToHunk, window, cx);
19453        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19454    });
19455    executor.run_until_parked();
19456    cx.assert_state_with_diff(
19457        r#"
19458        - use some::mod;
19459        + use some::modified;
19460
19461        - const A: u32 = 42;
19462          ˇ
19463          fn main() {
19464        -     println!("hello");
19465        +     println!("hello there");
19466
19467        +     println!("around the");
19468              println!("world");
19469          }
19470        "#
19471        .unindent(),
19472    );
19473
19474    cx.update_editor(|editor, window, cx| {
19475        editor.cancel(&Cancel, window, cx);
19476    });
19477
19478    cx.assert_state_with_diff(
19479        r#"
19480          use some::modified;
19481
19482          ˇ
19483          fn main() {
19484              println!("hello there");
19485
19486              println!("around the");
19487              println!("world");
19488          }
19489        "#
19490        .unindent(),
19491    );
19492}
19493
19494#[gpui::test]
19495async fn test_diff_base_change_with_expanded_diff_hunks(
19496    executor: BackgroundExecutor,
19497    cx: &mut TestAppContext,
19498) {
19499    init_test(cx, |_| {});
19500
19501    let mut cx = EditorTestContext::new(cx).await;
19502
19503    let diff_base = r#"
19504        use some::mod1;
19505        use some::mod2;
19506
19507        const A: u32 = 42;
19508        const B: u32 = 42;
19509        const C: u32 = 42;
19510
19511        fn main() {
19512            println!("hello");
19513
19514            println!("world");
19515        }
19516        "#
19517    .unindent();
19518
19519    cx.set_state(
19520        &r#"
19521        use some::mod2;
19522
19523        const A: u32 = 42;
19524        const C: u32 = 42;
19525
19526        fn main(ˇ) {
19527            //println!("hello");
19528
19529            println!("world");
19530            //
19531            //
19532        }
19533        "#
19534        .unindent(),
19535    );
19536
19537    cx.set_head_text(&diff_base);
19538    executor.run_until_parked();
19539
19540    cx.update_editor(|editor, window, cx| {
19541        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19542    });
19543    executor.run_until_parked();
19544    cx.assert_state_with_diff(
19545        r#"
19546        - use some::mod1;
19547          use some::mod2;
19548
19549          const A: u32 = 42;
19550        - const B: u32 = 42;
19551          const C: u32 = 42;
19552
19553          fn main(ˇ) {
19554        -     println!("hello");
19555        +     //println!("hello");
19556
19557              println!("world");
19558        +     //
19559        +     //
19560          }
19561        "#
19562        .unindent(),
19563    );
19564
19565    cx.set_head_text("new diff base!");
19566    executor.run_until_parked();
19567    cx.assert_state_with_diff(
19568        r#"
19569        - new diff base!
19570        + use some::mod2;
19571        +
19572        + const A: u32 = 42;
19573        + const C: u32 = 42;
19574        +
19575        + fn main(ˇ) {
19576        +     //println!("hello");
19577        +
19578        +     println!("world");
19579        +     //
19580        +     //
19581        + }
19582        "#
19583        .unindent(),
19584    );
19585}
19586
19587#[gpui::test]
19588async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
19589    init_test(cx, |_| {});
19590
19591    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19592    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19593    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19594    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19595    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
19596    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
19597
19598    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
19599    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
19600    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
19601
19602    let multi_buffer = cx.new(|cx| {
19603        let mut multibuffer = MultiBuffer::new(ReadWrite);
19604        multibuffer.push_excerpts(
19605            buffer_1.clone(),
19606            [
19607                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19608                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19609                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19610            ],
19611            cx,
19612        );
19613        multibuffer.push_excerpts(
19614            buffer_2.clone(),
19615            [
19616                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19617                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19618                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19619            ],
19620            cx,
19621        );
19622        multibuffer.push_excerpts(
19623            buffer_3.clone(),
19624            [
19625                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19626                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19627                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19628            ],
19629            cx,
19630        );
19631        multibuffer
19632    });
19633
19634    let editor =
19635        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19636    editor
19637        .update(cx, |editor, _window, cx| {
19638            for (buffer, diff_base) in [
19639                (buffer_1.clone(), file_1_old),
19640                (buffer_2.clone(), file_2_old),
19641                (buffer_3.clone(), file_3_old),
19642            ] {
19643                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
19644                editor
19645                    .buffer
19646                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
19647            }
19648        })
19649        .unwrap();
19650
19651    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19652    cx.run_until_parked();
19653
19654    cx.assert_editor_state(
19655        &"
19656            ˇaaa
19657            ccc
19658            ddd
19659
19660            ggg
19661            hhh
19662
19663
19664            lll
19665            mmm
19666            NNN
19667
19668            qqq
19669            rrr
19670
19671            uuu
19672            111
19673            222
19674            333
19675
19676            666
19677            777
19678
19679            000
19680            !!!"
19681        .unindent(),
19682    );
19683
19684    cx.update_editor(|editor, window, cx| {
19685        editor.select_all(&SelectAll, window, cx);
19686        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19687    });
19688    cx.executor().run_until_parked();
19689
19690    cx.assert_state_with_diff(
19691        "
19692            «aaa
19693          - bbb
19694            ccc
19695            ddd
19696
19697            ggg
19698            hhh
19699
19700
19701            lll
19702            mmm
19703          - nnn
19704          + NNN
19705
19706            qqq
19707            rrr
19708
19709            uuu
19710            111
19711            222
19712            333
19713
19714          + 666
19715            777
19716
19717            000
19718            !!!ˇ»"
19719            .unindent(),
19720    );
19721}
19722
19723#[gpui::test]
19724async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
19725    init_test(cx, |_| {});
19726
19727    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
19728    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
19729
19730    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
19731    let multi_buffer = cx.new(|cx| {
19732        let mut multibuffer = MultiBuffer::new(ReadWrite);
19733        multibuffer.push_excerpts(
19734            buffer.clone(),
19735            [
19736                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
19737                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
19738                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
19739            ],
19740            cx,
19741        );
19742        multibuffer
19743    });
19744
19745    let editor =
19746        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19747    editor
19748        .update(cx, |editor, _window, cx| {
19749            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
19750            editor
19751                .buffer
19752                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
19753        })
19754        .unwrap();
19755
19756    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19757    cx.run_until_parked();
19758
19759    cx.update_editor(|editor, window, cx| {
19760        editor.expand_all_diff_hunks(&Default::default(), window, cx)
19761    });
19762    cx.executor().run_until_parked();
19763
19764    // When the start of a hunk coincides with the start of its excerpt,
19765    // the hunk is expanded. When the start of a hunk is earlier than
19766    // the start of its excerpt, the hunk is not expanded.
19767    cx.assert_state_with_diff(
19768        "
19769            ˇaaa
19770          - bbb
19771          + BBB
19772
19773          - ddd
19774          - eee
19775          + DDD
19776          + EEE
19777            fff
19778
19779            iii
19780        "
19781        .unindent(),
19782    );
19783}
19784
19785#[gpui::test]
19786async fn test_edits_around_expanded_insertion_hunks(
19787    executor: BackgroundExecutor,
19788    cx: &mut TestAppContext,
19789) {
19790    init_test(cx, |_| {});
19791
19792    let mut cx = EditorTestContext::new(cx).await;
19793
19794    let diff_base = r#"
19795        use some::mod1;
19796        use some::mod2;
19797
19798        const A: u32 = 42;
19799
19800        fn main() {
19801            println!("hello");
19802
19803            println!("world");
19804        }
19805        "#
19806    .unindent();
19807    executor.run_until_parked();
19808    cx.set_state(
19809        &r#"
19810        use some::mod1;
19811        use some::mod2;
19812
19813        const A: u32 = 42;
19814        const B: u32 = 42;
19815        const C: u32 = 42;
19816        ˇ
19817
19818        fn main() {
19819            println!("hello");
19820
19821            println!("world");
19822        }
19823        "#
19824        .unindent(),
19825    );
19826
19827    cx.set_head_text(&diff_base);
19828    executor.run_until_parked();
19829
19830    cx.update_editor(|editor, window, cx| {
19831        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19832    });
19833    executor.run_until_parked();
19834
19835    cx.assert_state_with_diff(
19836        r#"
19837        use some::mod1;
19838        use some::mod2;
19839
19840        const A: u32 = 42;
19841      + const B: u32 = 42;
19842      + const C: 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 D: 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      + ˇ
19867
19868        fn main() {
19869            println!("hello");
19870
19871            println!("world");
19872        }
19873      "#
19874        .unindent(),
19875    );
19876
19877    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
19878    executor.run_until_parked();
19879
19880    cx.assert_state_with_diff(
19881        r#"
19882        use some::mod1;
19883        use some::mod2;
19884
19885        const A: u32 = 42;
19886      + const B: u32 = 42;
19887      + const C: u32 = 42;
19888      + const D: u32 = 42;
19889      + const E: u32 = 42;
19890      + ˇ
19891
19892        fn main() {
19893            println!("hello");
19894
19895            println!("world");
19896        }
19897      "#
19898        .unindent(),
19899    );
19900
19901    cx.update_editor(|editor, window, cx| {
19902        editor.delete_line(&DeleteLine, window, cx);
19903    });
19904    executor.run_until_parked();
19905
19906    cx.assert_state_with_diff(
19907        r#"
19908        use some::mod1;
19909        use some::mod2;
19910
19911        const A: u32 = 42;
19912      + const B: u32 = 42;
19913      + const C: u32 = 42;
19914      + const D: u32 = 42;
19915      + const E: u32 = 42;
19916        ˇ
19917        fn main() {
19918            println!("hello");
19919
19920            println!("world");
19921        }
19922      "#
19923        .unindent(),
19924    );
19925
19926    cx.update_editor(|editor, window, cx| {
19927        editor.move_up(&MoveUp, window, cx);
19928        editor.delete_line(&DeleteLine, window, cx);
19929        editor.move_up(&MoveUp, window, cx);
19930        editor.delete_line(&DeleteLine, window, cx);
19931        editor.move_up(&MoveUp, window, cx);
19932        editor.delete_line(&DeleteLine, window, cx);
19933    });
19934    executor.run_until_parked();
19935    cx.assert_state_with_diff(
19936        r#"
19937        use some::mod1;
19938        use some::mod2;
19939
19940        const A: u32 = 42;
19941      + const B: u32 = 42;
19942        ˇ
19943        fn main() {
19944            println!("hello");
19945
19946            println!("world");
19947        }
19948      "#
19949        .unindent(),
19950    );
19951
19952    cx.update_editor(|editor, window, cx| {
19953        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
19954        editor.delete_line(&DeleteLine, window, cx);
19955    });
19956    executor.run_until_parked();
19957    cx.assert_state_with_diff(
19958        r#"
19959        ˇ
19960        fn main() {
19961            println!("hello");
19962
19963            println!("world");
19964        }
19965      "#
19966        .unindent(),
19967    );
19968}
19969
19970#[gpui::test]
19971async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
19972    init_test(cx, |_| {});
19973
19974    let mut cx = EditorTestContext::new(cx).await;
19975    cx.set_head_text(indoc! { "
19976        one
19977        two
19978        three
19979        four
19980        five
19981        "
19982    });
19983    cx.set_state(indoc! { "
19984        one
19985        ˇthree
19986        five
19987    "});
19988    cx.run_until_parked();
19989    cx.update_editor(|editor, window, cx| {
19990        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19991    });
19992    cx.assert_state_with_diff(
19993        indoc! { "
19994        one
19995      - two
19996        ˇthree
19997      - four
19998        five
19999    "}
20000        .to_string(),
20001    );
20002    cx.update_editor(|editor, window, cx| {
20003        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20004    });
20005
20006    cx.assert_state_with_diff(
20007        indoc! { "
20008        one
20009        ˇthree
20010        five
20011    "}
20012        .to_string(),
20013    );
20014
20015    cx.set_state(indoc! { "
20016        one
20017        ˇTWO
20018        three
20019        four
20020        five
20021    "});
20022    cx.run_until_parked();
20023    cx.update_editor(|editor, window, cx| {
20024        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20025    });
20026
20027    cx.assert_state_with_diff(
20028        indoc! { "
20029            one
20030          - two
20031          + ˇTWO
20032            three
20033            four
20034            five
20035        "}
20036        .to_string(),
20037    );
20038    cx.update_editor(|editor, window, cx| {
20039        editor.move_up(&Default::default(), window, cx);
20040        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20041    });
20042    cx.assert_state_with_diff(
20043        indoc! { "
20044            one
20045            ˇTWO
20046            three
20047            four
20048            five
20049        "}
20050        .to_string(),
20051    );
20052}
20053
20054#[gpui::test]
20055async fn test_edits_around_expanded_deletion_hunks(
20056    executor: BackgroundExecutor,
20057    cx: &mut TestAppContext,
20058) {
20059    init_test(cx, |_| {});
20060
20061    let mut cx = EditorTestContext::new(cx).await;
20062
20063    let diff_base = r#"
20064        use some::mod1;
20065        use some::mod2;
20066
20067        const A: u32 = 42;
20068        const B: u32 = 42;
20069        const C: u32 = 42;
20070
20071
20072        fn main() {
20073            println!("hello");
20074
20075            println!("world");
20076        }
20077    "#
20078    .unindent();
20079    executor.run_until_parked();
20080    cx.set_state(
20081        &r#"
20082        use some::mod1;
20083        use some::mod2;
20084
20085        ˇconst B: u32 = 42;
20086        const C: u32 = 42;
20087
20088
20089        fn main() {
20090            println!("hello");
20091
20092            println!("world");
20093        }
20094        "#
20095        .unindent(),
20096    );
20097
20098    cx.set_head_text(&diff_base);
20099    executor.run_until_parked();
20100
20101    cx.update_editor(|editor, window, cx| {
20102        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20103    });
20104    executor.run_until_parked();
20105
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.delete_line(&DeleteLine, 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
20162        fn main() {
20163            println!("hello");
20164
20165            println!("world");
20166        }
20167      "#
20168        .unindent(),
20169    );
20170
20171    cx.update_editor(|editor, window, cx| {
20172        editor.handle_input("replacement", window, cx);
20173    });
20174    executor.run_until_parked();
20175    cx.assert_state_with_diff(
20176        r#"
20177        use some::mod1;
20178        use some::mod2;
20179
20180      - const A: u32 = 42;
20181      - const B: u32 = 42;
20182      - const C: u32 = 42;
20183      -
20184      + replacementˇ
20185
20186        fn main() {
20187            println!("hello");
20188
20189            println!("world");
20190        }
20191      "#
20192        .unindent(),
20193    );
20194}
20195
20196#[gpui::test]
20197async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20198    init_test(cx, |_| {});
20199
20200    let mut cx = EditorTestContext::new(cx).await;
20201
20202    let base_text = r#"
20203        one
20204        two
20205        three
20206        four
20207        five
20208    "#
20209    .unindent();
20210    executor.run_until_parked();
20211    cx.set_state(
20212        &r#"
20213        one
20214        two
20215        fˇour
20216        five
20217        "#
20218        .unindent(),
20219    );
20220
20221    cx.set_head_text(&base_text);
20222    executor.run_until_parked();
20223
20224    cx.update_editor(|editor, window, cx| {
20225        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20226    });
20227    executor.run_until_parked();
20228
20229    cx.assert_state_with_diff(
20230        r#"
20231          one
20232          two
20233        - three
20234          fˇour
20235          five
20236        "#
20237        .unindent(),
20238    );
20239
20240    cx.update_editor(|editor, window, cx| {
20241        editor.backspace(&Backspace, window, cx);
20242        editor.backspace(&Backspace, window, cx);
20243    });
20244    executor.run_until_parked();
20245    cx.assert_state_with_diff(
20246        r#"
20247          one
20248          two
20249        - threeˇ
20250        - four
20251        + our
20252          five
20253        "#
20254        .unindent(),
20255    );
20256}
20257
20258#[gpui::test]
20259async fn test_edit_after_expanded_modification_hunk(
20260    executor: BackgroundExecutor,
20261    cx: &mut TestAppContext,
20262) {
20263    init_test(cx, |_| {});
20264
20265    let mut cx = EditorTestContext::new(cx).await;
20266
20267    let diff_base = r#"
20268        use some::mod1;
20269        use some::mod2;
20270
20271        const A: u32 = 42;
20272        const B: u32 = 42;
20273        const C: u32 = 42;
20274        const D: u32 = 42;
20275
20276
20277        fn main() {
20278            println!("hello");
20279
20280            println!("world");
20281        }"#
20282    .unindent();
20283
20284    cx.set_state(
20285        &r#"
20286        use some::mod1;
20287        use some::mod2;
20288
20289        const A: u32 = 42;
20290        const B: u32 = 42;
20291        const C: u32 = 43ˇ
20292        const D: u32 = 42;
20293
20294
20295        fn main() {
20296            println!("hello");
20297
20298            println!("world");
20299        }"#
20300        .unindent(),
20301    );
20302
20303    cx.set_head_text(&diff_base);
20304    executor.run_until_parked();
20305    cx.update_editor(|editor, window, cx| {
20306        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20307    });
20308    executor.run_until_parked();
20309
20310    cx.assert_state_with_diff(
20311        r#"
20312        use some::mod1;
20313        use some::mod2;
20314
20315        const A: u32 = 42;
20316        const B: u32 = 42;
20317      - const C: u32 = 42;
20318      + const C: u32 = 43ˇ
20319        const D: u32 = 42;
20320
20321
20322        fn main() {
20323            println!("hello");
20324
20325            println!("world");
20326        }"#
20327        .unindent(),
20328    );
20329
20330    cx.update_editor(|editor, window, cx| {
20331        editor.handle_input("\nnew_line\n", window, cx);
20332    });
20333    executor.run_until_parked();
20334
20335    cx.assert_state_with_diff(
20336        r#"
20337        use some::mod1;
20338        use some::mod2;
20339
20340        const A: u32 = 42;
20341        const B: u32 = 42;
20342      - const C: u32 = 42;
20343      + const C: u32 = 43
20344      + new_line
20345      + ˇ
20346        const D: u32 = 42;
20347
20348
20349        fn main() {
20350            println!("hello");
20351
20352            println!("world");
20353        }"#
20354        .unindent(),
20355    );
20356}
20357
20358#[gpui::test]
20359async fn test_stage_and_unstage_added_file_hunk(
20360    executor: BackgroundExecutor,
20361    cx: &mut TestAppContext,
20362) {
20363    init_test(cx, |_| {});
20364
20365    let mut cx = EditorTestContext::new(cx).await;
20366    cx.update_editor(|editor, _, cx| {
20367        editor.set_expand_all_diff_hunks(cx);
20368    });
20369
20370    let working_copy = r#"
20371            ˇfn main() {
20372                println!("hello, world!");
20373            }
20374        "#
20375    .unindent();
20376
20377    cx.set_state(&working_copy);
20378    executor.run_until_parked();
20379
20380    cx.assert_state_with_diff(
20381        r#"
20382            + ˇfn main() {
20383            +     println!("hello, world!");
20384            + }
20385        "#
20386        .unindent(),
20387    );
20388    cx.assert_index_text(None);
20389
20390    cx.update_editor(|editor, window, cx| {
20391        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20392    });
20393    executor.run_until_parked();
20394    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
20395    cx.assert_state_with_diff(
20396        r#"
20397            + ˇfn main() {
20398            +     println!("hello, world!");
20399            + }
20400        "#
20401        .unindent(),
20402    );
20403
20404    cx.update_editor(|editor, window, cx| {
20405        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20406    });
20407    executor.run_until_parked();
20408    cx.assert_index_text(None);
20409}
20410
20411async fn setup_indent_guides_editor(
20412    text: &str,
20413    cx: &mut TestAppContext,
20414) -> (BufferId, EditorTestContext) {
20415    init_test(cx, |_| {});
20416
20417    let mut cx = EditorTestContext::new(cx).await;
20418
20419    let buffer_id = cx.update_editor(|editor, window, cx| {
20420        editor.set_text(text, window, cx);
20421        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
20422
20423        buffer_ids[0]
20424    });
20425
20426    (buffer_id, cx)
20427}
20428
20429fn assert_indent_guides(
20430    range: Range<u32>,
20431    expected: Vec<IndentGuide>,
20432    active_indices: Option<Vec<usize>>,
20433    cx: &mut EditorTestContext,
20434) {
20435    let indent_guides = cx.update_editor(|editor, window, cx| {
20436        let snapshot = editor.snapshot(window, cx).display_snapshot;
20437        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
20438            editor,
20439            MultiBufferRow(range.start)..MultiBufferRow(range.end),
20440            true,
20441            &snapshot,
20442            cx,
20443        );
20444
20445        indent_guides.sort_by(|a, b| {
20446            a.depth.cmp(&b.depth).then(
20447                a.start_row
20448                    .cmp(&b.start_row)
20449                    .then(a.end_row.cmp(&b.end_row)),
20450            )
20451        });
20452        indent_guides
20453    });
20454
20455    if let Some(expected) = active_indices {
20456        let active_indices = cx.update_editor(|editor, window, cx| {
20457            let snapshot = editor.snapshot(window, cx).display_snapshot;
20458            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
20459        });
20460
20461        assert_eq!(
20462            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
20463            expected,
20464            "Active indent guide indices do not match"
20465        );
20466    }
20467
20468    assert_eq!(indent_guides, expected, "Indent guides do not match");
20469}
20470
20471fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
20472    IndentGuide {
20473        buffer_id,
20474        start_row: MultiBufferRow(start_row),
20475        end_row: MultiBufferRow(end_row),
20476        depth,
20477        tab_size: 4,
20478        settings: IndentGuideSettings {
20479            enabled: true,
20480            line_width: 1,
20481            active_line_width: 1,
20482            coloring: IndentGuideColoring::default(),
20483            background_coloring: IndentGuideBackgroundColoring::default(),
20484        },
20485    }
20486}
20487
20488#[gpui::test]
20489async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
20490    let (buffer_id, mut cx) = setup_indent_guides_editor(
20491        &"
20492        fn main() {
20493            let a = 1;
20494        }"
20495        .unindent(),
20496        cx,
20497    )
20498    .await;
20499
20500    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20501}
20502
20503#[gpui::test]
20504async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
20505    let (buffer_id, mut cx) = setup_indent_guides_editor(
20506        &"
20507        fn main() {
20508            let a = 1;
20509            let b = 2;
20510        }"
20511        .unindent(),
20512        cx,
20513    )
20514    .await;
20515
20516    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
20517}
20518
20519#[gpui::test]
20520async fn test_indent_guide_nested(cx: &mut TestAppContext) {
20521    let (buffer_id, mut cx) = setup_indent_guides_editor(
20522        &"
20523        fn main() {
20524            let a = 1;
20525            if a == 3 {
20526                let b = 2;
20527            } else {
20528                let c = 3;
20529            }
20530        }"
20531        .unindent(),
20532        cx,
20533    )
20534    .await;
20535
20536    assert_indent_guides(
20537        0..8,
20538        vec![
20539            indent_guide(buffer_id, 1, 6, 0),
20540            indent_guide(buffer_id, 3, 3, 1),
20541            indent_guide(buffer_id, 5, 5, 1),
20542        ],
20543        None,
20544        &mut cx,
20545    );
20546}
20547
20548#[gpui::test]
20549async fn test_indent_guide_tab(cx: &mut TestAppContext) {
20550    let (buffer_id, mut cx) = setup_indent_guides_editor(
20551        &"
20552        fn main() {
20553            let a = 1;
20554                let b = 2;
20555            let c = 3;
20556        }"
20557        .unindent(),
20558        cx,
20559    )
20560    .await;
20561
20562    assert_indent_guides(
20563        0..5,
20564        vec![
20565            indent_guide(buffer_id, 1, 3, 0),
20566            indent_guide(buffer_id, 2, 2, 1),
20567        ],
20568        None,
20569        &mut cx,
20570    );
20571}
20572
20573#[gpui::test]
20574async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
20575    let (buffer_id, mut cx) = setup_indent_guides_editor(
20576        &"
20577        fn main() {
20578            let a = 1;
20579
20580            let c = 3;
20581        }"
20582        .unindent(),
20583        cx,
20584    )
20585    .await;
20586
20587    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
20588}
20589
20590#[gpui::test]
20591async fn test_indent_guide_complex(cx: &mut TestAppContext) {
20592    let (buffer_id, mut cx) = setup_indent_guides_editor(
20593        &"
20594        fn main() {
20595            let a = 1;
20596
20597            let c = 3;
20598
20599            if a == 3 {
20600                let b = 2;
20601            } else {
20602                let c = 3;
20603            }
20604        }"
20605        .unindent(),
20606        cx,
20607    )
20608    .await;
20609
20610    assert_indent_guides(
20611        0..11,
20612        vec![
20613            indent_guide(buffer_id, 1, 9, 0),
20614            indent_guide(buffer_id, 6, 6, 1),
20615            indent_guide(buffer_id, 8, 8, 1),
20616        ],
20617        None,
20618        &mut cx,
20619    );
20620}
20621
20622#[gpui::test]
20623async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
20624    let (buffer_id, mut cx) = setup_indent_guides_editor(
20625        &"
20626        fn main() {
20627            let a = 1;
20628
20629            let c = 3;
20630
20631            if a == 3 {
20632                let b = 2;
20633            } else {
20634                let c = 3;
20635            }
20636        }"
20637        .unindent(),
20638        cx,
20639    )
20640    .await;
20641
20642    assert_indent_guides(
20643        1..11,
20644        vec![
20645            indent_guide(buffer_id, 1, 9, 0),
20646            indent_guide(buffer_id, 6, 6, 1),
20647            indent_guide(buffer_id, 8, 8, 1),
20648        ],
20649        None,
20650        &mut cx,
20651    );
20652}
20653
20654#[gpui::test]
20655async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
20656    let (buffer_id, mut cx) = setup_indent_guides_editor(
20657        &"
20658        fn main() {
20659            let a = 1;
20660
20661            let c = 3;
20662
20663            if a == 3 {
20664                let b = 2;
20665            } else {
20666                let c = 3;
20667            }
20668        }"
20669        .unindent(),
20670        cx,
20671    )
20672    .await;
20673
20674    assert_indent_guides(
20675        1..10,
20676        vec![
20677            indent_guide(buffer_id, 1, 9, 0),
20678            indent_guide(buffer_id, 6, 6, 1),
20679            indent_guide(buffer_id, 8, 8, 1),
20680        ],
20681        None,
20682        &mut cx,
20683    );
20684}
20685
20686#[gpui::test]
20687async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
20688    let (buffer_id, mut cx) = setup_indent_guides_editor(
20689        &"
20690        fn main() {
20691            if a {
20692                b(
20693                    c,
20694                    d,
20695                )
20696            } else {
20697                e(
20698                    f
20699                )
20700            }
20701        }"
20702        .unindent(),
20703        cx,
20704    )
20705    .await;
20706
20707    assert_indent_guides(
20708        0..11,
20709        vec![
20710            indent_guide(buffer_id, 1, 10, 0),
20711            indent_guide(buffer_id, 2, 5, 1),
20712            indent_guide(buffer_id, 7, 9, 1),
20713            indent_guide(buffer_id, 3, 4, 2),
20714            indent_guide(buffer_id, 8, 8, 2),
20715        ],
20716        None,
20717        &mut cx,
20718    );
20719
20720    cx.update_editor(|editor, window, cx| {
20721        editor.fold_at(MultiBufferRow(2), window, cx);
20722        assert_eq!(
20723            editor.display_text(cx),
20724            "
20725            fn main() {
20726                if a {
20727                    b(⋯
20728                    )
20729                } else {
20730                    e(
20731                        f
20732                    )
20733                }
20734            }"
20735            .unindent()
20736        );
20737    });
20738
20739    assert_indent_guides(
20740        0..11,
20741        vec![
20742            indent_guide(buffer_id, 1, 10, 0),
20743            indent_guide(buffer_id, 2, 5, 1),
20744            indent_guide(buffer_id, 7, 9, 1),
20745            indent_guide(buffer_id, 8, 8, 2),
20746        ],
20747        None,
20748        &mut cx,
20749    );
20750}
20751
20752#[gpui::test]
20753async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
20754    let (buffer_id, mut cx) = setup_indent_guides_editor(
20755        &"
20756        block1
20757            block2
20758                block3
20759                    block4
20760            block2
20761        block1
20762        block1"
20763            .unindent(),
20764        cx,
20765    )
20766    .await;
20767
20768    assert_indent_guides(
20769        1..10,
20770        vec![
20771            indent_guide(buffer_id, 1, 4, 0),
20772            indent_guide(buffer_id, 2, 3, 1),
20773            indent_guide(buffer_id, 3, 3, 2),
20774        ],
20775        None,
20776        &mut cx,
20777    );
20778}
20779
20780#[gpui::test]
20781async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
20782    let (buffer_id, mut cx) = setup_indent_guides_editor(
20783        &"
20784        block1
20785            block2
20786                block3
20787
20788        block1
20789        block1"
20790            .unindent(),
20791        cx,
20792    )
20793    .await;
20794
20795    assert_indent_guides(
20796        0..6,
20797        vec![
20798            indent_guide(buffer_id, 1, 2, 0),
20799            indent_guide(buffer_id, 2, 2, 1),
20800        ],
20801        None,
20802        &mut cx,
20803    );
20804}
20805
20806#[gpui::test]
20807async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
20808    let (buffer_id, mut cx) = setup_indent_guides_editor(
20809        &"
20810        function component() {
20811        \treturn (
20812        \t\t\t
20813        \t\t<div>
20814        \t\t\t<abc></abc>
20815        \t\t</div>
20816        \t)
20817        }"
20818        .unindent(),
20819        cx,
20820    )
20821    .await;
20822
20823    assert_indent_guides(
20824        0..8,
20825        vec![
20826            indent_guide(buffer_id, 1, 6, 0),
20827            indent_guide(buffer_id, 2, 5, 1),
20828            indent_guide(buffer_id, 4, 4, 2),
20829        ],
20830        None,
20831        &mut cx,
20832    );
20833}
20834
20835#[gpui::test]
20836async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
20837    let (buffer_id, mut cx) = setup_indent_guides_editor(
20838        &"
20839        function component() {
20840        \treturn (
20841        \t
20842        \t\t<div>
20843        \t\t\t<abc></abc>
20844        \t\t</div>
20845        \t)
20846        }"
20847        .unindent(),
20848        cx,
20849    )
20850    .await;
20851
20852    assert_indent_guides(
20853        0..8,
20854        vec![
20855            indent_guide(buffer_id, 1, 6, 0),
20856            indent_guide(buffer_id, 2, 5, 1),
20857            indent_guide(buffer_id, 4, 4, 2),
20858        ],
20859        None,
20860        &mut cx,
20861    );
20862}
20863
20864#[gpui::test]
20865async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
20866    let (buffer_id, mut cx) = setup_indent_guides_editor(
20867        &"
20868        block1
20869
20870
20871
20872            block2
20873        "
20874        .unindent(),
20875        cx,
20876    )
20877    .await;
20878
20879    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20880}
20881
20882#[gpui::test]
20883async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
20884    let (buffer_id, mut cx) = setup_indent_guides_editor(
20885        &"
20886        def a:
20887        \tb = 3
20888        \tif True:
20889        \t\tc = 4
20890        \t\td = 5
20891        \tprint(b)
20892        "
20893        .unindent(),
20894        cx,
20895    )
20896    .await;
20897
20898    assert_indent_guides(
20899        0..6,
20900        vec![
20901            indent_guide(buffer_id, 1, 5, 0),
20902            indent_guide(buffer_id, 3, 4, 1),
20903        ],
20904        None,
20905        &mut cx,
20906    );
20907}
20908
20909#[gpui::test]
20910async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
20911    let (buffer_id, mut cx) = setup_indent_guides_editor(
20912        &"
20913    fn main() {
20914        let a = 1;
20915    }"
20916        .unindent(),
20917        cx,
20918    )
20919    .await;
20920
20921    cx.update_editor(|editor, window, cx| {
20922        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20923            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20924        });
20925    });
20926
20927    assert_indent_guides(
20928        0..3,
20929        vec![indent_guide(buffer_id, 1, 1, 0)],
20930        Some(vec![0]),
20931        &mut cx,
20932    );
20933}
20934
20935#[gpui::test]
20936async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
20937    let (buffer_id, mut cx) = setup_indent_guides_editor(
20938        &"
20939    fn main() {
20940        if 1 == 2 {
20941            let a = 1;
20942        }
20943    }"
20944        .unindent(),
20945        cx,
20946    )
20947    .await;
20948
20949    cx.update_editor(|editor, window, cx| {
20950        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20951            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20952        });
20953    });
20954
20955    assert_indent_guides(
20956        0..4,
20957        vec![
20958            indent_guide(buffer_id, 1, 3, 0),
20959            indent_guide(buffer_id, 2, 2, 1),
20960        ],
20961        Some(vec![1]),
20962        &mut cx,
20963    );
20964
20965    cx.update_editor(|editor, window, cx| {
20966        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20967            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20968        });
20969    });
20970
20971    assert_indent_guides(
20972        0..4,
20973        vec![
20974            indent_guide(buffer_id, 1, 3, 0),
20975            indent_guide(buffer_id, 2, 2, 1),
20976        ],
20977        Some(vec![1]),
20978        &mut cx,
20979    );
20980
20981    cx.update_editor(|editor, window, cx| {
20982        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20983            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
20984        });
20985    });
20986
20987    assert_indent_guides(
20988        0..4,
20989        vec![
20990            indent_guide(buffer_id, 1, 3, 0),
20991            indent_guide(buffer_id, 2, 2, 1),
20992        ],
20993        Some(vec![0]),
20994        &mut cx,
20995    );
20996}
20997
20998#[gpui::test]
20999async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
21000    let (buffer_id, mut cx) = setup_indent_guides_editor(
21001        &"
21002    fn main() {
21003        let a = 1;
21004
21005        let b = 2;
21006    }"
21007        .unindent(),
21008        cx,
21009    )
21010    .await;
21011
21012    cx.update_editor(|editor, window, cx| {
21013        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21014            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
21015        });
21016    });
21017
21018    assert_indent_guides(
21019        0..5,
21020        vec![indent_guide(buffer_id, 1, 3, 0)],
21021        Some(vec![0]),
21022        &mut cx,
21023    );
21024}
21025
21026#[gpui::test]
21027async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
21028    let (buffer_id, mut cx) = setup_indent_guides_editor(
21029        &"
21030    def m:
21031        a = 1
21032        pass"
21033            .unindent(),
21034        cx,
21035    )
21036    .await;
21037
21038    cx.update_editor(|editor, window, cx| {
21039        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21040            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
21041        });
21042    });
21043
21044    assert_indent_guides(
21045        0..3,
21046        vec![indent_guide(buffer_id, 1, 2, 0)],
21047        Some(vec![0]),
21048        &mut cx,
21049    );
21050}
21051
21052#[gpui::test]
21053async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
21054    init_test(cx, |_| {});
21055    let mut cx = EditorTestContext::new(cx).await;
21056    let text = indoc! {
21057        "
21058        impl A {
21059            fn b() {
21060                0;
21061                3;
21062                5;
21063                6;
21064                7;
21065            }
21066        }
21067        "
21068    };
21069    let base_text = indoc! {
21070        "
21071        impl A {
21072            fn b() {
21073                0;
21074                1;
21075                2;
21076                3;
21077                4;
21078            }
21079            fn c() {
21080                5;
21081                6;
21082                7;
21083            }
21084        }
21085        "
21086    };
21087
21088    cx.update_editor(|editor, window, cx| {
21089        editor.set_text(text, window, cx);
21090
21091        editor.buffer().update(cx, |multibuffer, cx| {
21092            let buffer = multibuffer.as_singleton().unwrap();
21093            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
21094
21095            multibuffer.set_all_diff_hunks_expanded(cx);
21096            multibuffer.add_diff(diff, cx);
21097
21098            buffer.read(cx).remote_id()
21099        })
21100    });
21101    cx.run_until_parked();
21102
21103    cx.assert_state_with_diff(
21104        indoc! { "
21105          impl A {
21106              fn b() {
21107                  0;
21108        -         1;
21109        -         2;
21110                  3;
21111        -         4;
21112        -     }
21113        -     fn c() {
21114                  5;
21115                  6;
21116                  7;
21117              }
21118          }
21119          ˇ"
21120        }
21121        .to_string(),
21122    );
21123
21124    let mut actual_guides = cx.update_editor(|editor, window, cx| {
21125        editor
21126            .snapshot(window, cx)
21127            .buffer_snapshot()
21128            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
21129            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
21130            .collect::<Vec<_>>()
21131    });
21132    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
21133    assert_eq!(
21134        actual_guides,
21135        vec![
21136            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
21137            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
21138            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
21139        ]
21140    );
21141}
21142
21143#[gpui::test]
21144async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
21145    init_test(cx, |_| {});
21146    let mut cx = EditorTestContext::new(cx).await;
21147
21148    let diff_base = r#"
21149        a
21150        b
21151        c
21152        "#
21153    .unindent();
21154
21155    cx.set_state(
21156        &r#"
21157        ˇA
21158        b
21159        C
21160        "#
21161        .unindent(),
21162    );
21163    cx.set_head_text(&diff_base);
21164    cx.update_editor(|editor, window, cx| {
21165        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21166    });
21167    executor.run_until_parked();
21168
21169    let both_hunks_expanded = r#"
21170        - a
21171        + ˇA
21172          b
21173        - c
21174        + C
21175        "#
21176    .unindent();
21177
21178    cx.assert_state_with_diff(both_hunks_expanded.clone());
21179
21180    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21181        let snapshot = editor.snapshot(window, cx);
21182        let hunks = editor
21183            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21184            .collect::<Vec<_>>();
21185        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21186        let buffer_id = hunks[0].buffer_id;
21187        hunks
21188            .into_iter()
21189            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
21190            .collect::<Vec<_>>()
21191    });
21192    assert_eq!(hunk_ranges.len(), 2);
21193
21194    cx.update_editor(|editor, _, cx| {
21195        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21196    });
21197    executor.run_until_parked();
21198
21199    let second_hunk_expanded = r#"
21200          ˇA
21201          b
21202        - c
21203        + C
21204        "#
21205    .unindent();
21206
21207    cx.assert_state_with_diff(second_hunk_expanded);
21208
21209    cx.update_editor(|editor, _, cx| {
21210        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21211    });
21212    executor.run_until_parked();
21213
21214    cx.assert_state_with_diff(both_hunks_expanded.clone());
21215
21216    cx.update_editor(|editor, _, cx| {
21217        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21218    });
21219    executor.run_until_parked();
21220
21221    let first_hunk_expanded = r#"
21222        - a
21223        + ˇA
21224          b
21225          C
21226        "#
21227    .unindent();
21228
21229    cx.assert_state_with_diff(first_hunk_expanded);
21230
21231    cx.update_editor(|editor, _, cx| {
21232        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21233    });
21234    executor.run_until_parked();
21235
21236    cx.assert_state_with_diff(both_hunks_expanded);
21237
21238    cx.set_state(
21239        &r#"
21240        ˇA
21241        b
21242        "#
21243        .unindent(),
21244    );
21245    cx.run_until_parked();
21246
21247    // TODO this cursor position seems bad
21248    cx.assert_state_with_diff(
21249        r#"
21250        - ˇa
21251        + A
21252          b
21253        "#
21254        .unindent(),
21255    );
21256
21257    cx.update_editor(|editor, window, cx| {
21258        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21259    });
21260
21261    cx.assert_state_with_diff(
21262        r#"
21263            - ˇa
21264            + A
21265              b
21266            - c
21267            "#
21268        .unindent(),
21269    );
21270
21271    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21272        let snapshot = editor.snapshot(window, cx);
21273        let hunks = editor
21274            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21275            .collect::<Vec<_>>();
21276        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21277        let buffer_id = hunks[0].buffer_id;
21278        hunks
21279            .into_iter()
21280            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
21281            .collect::<Vec<_>>()
21282    });
21283    assert_eq!(hunk_ranges.len(), 2);
21284
21285    cx.update_editor(|editor, _, cx| {
21286        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21287    });
21288    executor.run_until_parked();
21289
21290    cx.assert_state_with_diff(
21291        r#"
21292        - ˇa
21293        + A
21294          b
21295        "#
21296        .unindent(),
21297    );
21298}
21299
21300#[gpui::test]
21301async fn test_toggle_deletion_hunk_at_start_of_file(
21302    executor: BackgroundExecutor,
21303    cx: &mut TestAppContext,
21304) {
21305    init_test(cx, |_| {});
21306    let mut cx = EditorTestContext::new(cx).await;
21307
21308    let diff_base = r#"
21309        a
21310        b
21311        c
21312        "#
21313    .unindent();
21314
21315    cx.set_state(
21316        &r#"
21317        ˇb
21318        c
21319        "#
21320        .unindent(),
21321    );
21322    cx.set_head_text(&diff_base);
21323    cx.update_editor(|editor, window, cx| {
21324        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21325    });
21326    executor.run_until_parked();
21327
21328    let hunk_expanded = r#"
21329        - a
21330          ˇb
21331          c
21332        "#
21333    .unindent();
21334
21335    cx.assert_state_with_diff(hunk_expanded.clone());
21336
21337    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21338        let snapshot = editor.snapshot(window, cx);
21339        let hunks = editor
21340            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21341            .collect::<Vec<_>>();
21342        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21343        let buffer_id = hunks[0].buffer_id;
21344        hunks
21345            .into_iter()
21346            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
21347            .collect::<Vec<_>>()
21348    });
21349    assert_eq!(hunk_ranges.len(), 1);
21350
21351    cx.update_editor(|editor, _, cx| {
21352        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21353    });
21354    executor.run_until_parked();
21355
21356    let hunk_collapsed = r#"
21357          ˇb
21358          c
21359        "#
21360    .unindent();
21361
21362    cx.assert_state_with_diff(hunk_collapsed);
21363
21364    cx.update_editor(|editor, _, cx| {
21365        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21366    });
21367    executor.run_until_parked();
21368
21369    cx.assert_state_with_diff(hunk_expanded);
21370}
21371
21372#[gpui::test]
21373async fn test_display_diff_hunks(cx: &mut TestAppContext) {
21374    init_test(cx, |_| {});
21375
21376    let fs = FakeFs::new(cx.executor());
21377    fs.insert_tree(
21378        path!("/test"),
21379        json!({
21380            ".git": {},
21381            "file-1": "ONE\n",
21382            "file-2": "TWO\n",
21383            "file-3": "THREE\n",
21384        }),
21385    )
21386    .await;
21387
21388    fs.set_head_for_repo(
21389        path!("/test/.git").as_ref(),
21390        &[
21391            ("file-1", "one\n".into()),
21392            ("file-2", "two\n".into()),
21393            ("file-3", "three\n".into()),
21394        ],
21395        "deadbeef",
21396    );
21397
21398    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
21399    let mut buffers = vec![];
21400    for i in 1..=3 {
21401        let buffer = project
21402            .update(cx, |project, cx| {
21403                let path = format!(path!("/test/file-{}"), i);
21404                project.open_local_buffer(path, cx)
21405            })
21406            .await
21407            .unwrap();
21408        buffers.push(buffer);
21409    }
21410
21411    let multibuffer = cx.new(|cx| {
21412        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
21413        multibuffer.set_all_diff_hunks_expanded(cx);
21414        for buffer in &buffers {
21415            let snapshot = buffer.read(cx).snapshot();
21416            multibuffer.set_excerpts_for_path(
21417                PathKey::with_sort_prefix(0, buffer.read(cx).file().unwrap().path().clone()),
21418                buffer.clone(),
21419                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
21420                2,
21421                cx,
21422            );
21423        }
21424        multibuffer
21425    });
21426
21427    let editor = cx.add_window(|window, cx| {
21428        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
21429    });
21430    cx.run_until_parked();
21431
21432    let snapshot = editor
21433        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21434        .unwrap();
21435    let hunks = snapshot
21436        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
21437        .map(|hunk| match hunk {
21438            DisplayDiffHunk::Unfolded {
21439                display_row_range, ..
21440            } => display_row_range,
21441            DisplayDiffHunk::Folded { .. } => unreachable!(),
21442        })
21443        .collect::<Vec<_>>();
21444    assert_eq!(
21445        hunks,
21446        [
21447            DisplayRow(2)..DisplayRow(4),
21448            DisplayRow(7)..DisplayRow(9),
21449            DisplayRow(12)..DisplayRow(14),
21450        ]
21451    );
21452}
21453
21454#[gpui::test]
21455async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
21456    init_test(cx, |_| {});
21457
21458    let mut cx = EditorTestContext::new(cx).await;
21459    cx.set_head_text(indoc! { "
21460        one
21461        two
21462        three
21463        four
21464        five
21465        "
21466    });
21467    cx.set_index_text(indoc! { "
21468        one
21469        two
21470        three
21471        four
21472        five
21473        "
21474    });
21475    cx.set_state(indoc! {"
21476        one
21477        TWO
21478        ˇTHREE
21479        FOUR
21480        five
21481    "});
21482    cx.run_until_parked();
21483    cx.update_editor(|editor, window, cx| {
21484        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21485    });
21486    cx.run_until_parked();
21487    cx.assert_index_text(Some(indoc! {"
21488        one
21489        TWO
21490        THREE
21491        FOUR
21492        five
21493    "}));
21494    cx.set_state(indoc! { "
21495        one
21496        TWO
21497        ˇTHREE-HUNDRED
21498        FOUR
21499        five
21500    "});
21501    cx.run_until_parked();
21502    cx.update_editor(|editor, window, cx| {
21503        let snapshot = editor.snapshot(window, cx);
21504        let hunks = editor
21505            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21506            .collect::<Vec<_>>();
21507        assert_eq!(hunks.len(), 1);
21508        assert_eq!(
21509            hunks[0].status(),
21510            DiffHunkStatus {
21511                kind: DiffHunkStatusKind::Modified,
21512                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
21513            }
21514        );
21515
21516        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21517    });
21518    cx.run_until_parked();
21519    cx.assert_index_text(Some(indoc! {"
21520        one
21521        TWO
21522        THREE-HUNDRED
21523        FOUR
21524        five
21525    "}));
21526}
21527
21528#[gpui::test]
21529fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
21530    init_test(cx, |_| {});
21531
21532    let editor = cx.add_window(|window, cx| {
21533        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
21534        build_editor(buffer, window, cx)
21535    });
21536
21537    let render_args = Arc::new(Mutex::new(None));
21538    let snapshot = editor
21539        .update(cx, |editor, window, cx| {
21540            let snapshot = editor.buffer().read(cx).snapshot(cx);
21541            let range =
21542                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
21543
21544            struct RenderArgs {
21545                row: MultiBufferRow,
21546                folded: bool,
21547                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
21548            }
21549
21550            let crease = Crease::inline(
21551                range,
21552                FoldPlaceholder::test(),
21553                {
21554                    let toggle_callback = render_args.clone();
21555                    move |row, folded, callback, _window, _cx| {
21556                        *toggle_callback.lock() = Some(RenderArgs {
21557                            row,
21558                            folded,
21559                            callback,
21560                        });
21561                        div()
21562                    }
21563                },
21564                |_row, _folded, _window, _cx| div(),
21565            );
21566
21567            editor.insert_creases(Some(crease), cx);
21568            let snapshot = editor.snapshot(window, cx);
21569            let _div =
21570                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
21571            snapshot
21572        })
21573        .unwrap();
21574
21575    let render_args = render_args.lock().take().unwrap();
21576    assert_eq!(render_args.row, MultiBufferRow(1));
21577    assert!(!render_args.folded);
21578    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21579
21580    cx.update_window(*editor, |_, window, cx| {
21581        (render_args.callback)(true, window, cx)
21582    })
21583    .unwrap();
21584    let snapshot = editor
21585        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21586        .unwrap();
21587    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
21588
21589    cx.update_window(*editor, |_, window, cx| {
21590        (render_args.callback)(false, window, cx)
21591    })
21592    .unwrap();
21593    let snapshot = editor
21594        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21595        .unwrap();
21596    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21597}
21598
21599#[gpui::test]
21600async fn test_input_text(cx: &mut TestAppContext) {
21601    init_test(cx, |_| {});
21602    let mut cx = EditorTestContext::new(cx).await;
21603
21604    cx.set_state(
21605        &r#"ˇone
21606        two
21607
21608        three
21609        fourˇ
21610        five
21611
21612        siˇx"#
21613            .unindent(),
21614    );
21615
21616    cx.dispatch_action(HandleInput(String::new()));
21617    cx.assert_editor_state(
21618        &r#"ˇone
21619        two
21620
21621        three
21622        fourˇ
21623        five
21624
21625        siˇx"#
21626            .unindent(),
21627    );
21628
21629    cx.dispatch_action(HandleInput("AAAA".to_string()));
21630    cx.assert_editor_state(
21631        &r#"AAAAˇone
21632        two
21633
21634        three
21635        fourAAAAˇ
21636        five
21637
21638        siAAAAˇx"#
21639            .unindent(),
21640    );
21641}
21642
21643#[gpui::test]
21644async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
21645    init_test(cx, |_| {});
21646
21647    let mut cx = EditorTestContext::new(cx).await;
21648    cx.set_state(
21649        r#"let foo = 1;
21650let foo = 2;
21651let foo = 3;
21652let fooˇ = 4;
21653let foo = 5;
21654let foo = 6;
21655let foo = 7;
21656let foo = 8;
21657let foo = 9;
21658let foo = 10;
21659let foo = 11;
21660let foo = 12;
21661let foo = 13;
21662let foo = 14;
21663let foo = 15;"#,
21664    );
21665
21666    cx.update_editor(|e, window, cx| {
21667        assert_eq!(
21668            e.next_scroll_position,
21669            NextScrollCursorCenterTopBottom::Center,
21670            "Default next scroll direction is center",
21671        );
21672
21673        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21674        assert_eq!(
21675            e.next_scroll_position,
21676            NextScrollCursorCenterTopBottom::Top,
21677            "After center, next scroll direction should be top",
21678        );
21679
21680        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21681        assert_eq!(
21682            e.next_scroll_position,
21683            NextScrollCursorCenterTopBottom::Bottom,
21684            "After top, next scroll direction should be bottom",
21685        );
21686
21687        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21688        assert_eq!(
21689            e.next_scroll_position,
21690            NextScrollCursorCenterTopBottom::Center,
21691            "After bottom, scrolling should start over",
21692        );
21693
21694        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21695        assert_eq!(
21696            e.next_scroll_position,
21697            NextScrollCursorCenterTopBottom::Top,
21698            "Scrolling continues if retriggered fast enough"
21699        );
21700    });
21701
21702    cx.executor()
21703        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
21704    cx.executor().run_until_parked();
21705    cx.update_editor(|e, _, _| {
21706        assert_eq!(
21707            e.next_scroll_position,
21708            NextScrollCursorCenterTopBottom::Center,
21709            "If scrolling is not triggered fast enough, it should reset"
21710        );
21711    });
21712}
21713
21714#[gpui::test]
21715async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
21716    init_test(cx, |_| {});
21717    let mut cx = EditorLspTestContext::new_rust(
21718        lsp::ServerCapabilities {
21719            definition_provider: Some(lsp::OneOf::Left(true)),
21720            references_provider: Some(lsp::OneOf::Left(true)),
21721            ..lsp::ServerCapabilities::default()
21722        },
21723        cx,
21724    )
21725    .await;
21726
21727    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
21728        let go_to_definition = cx
21729            .lsp
21730            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21731                move |params, _| async move {
21732                    if empty_go_to_definition {
21733                        Ok(None)
21734                    } else {
21735                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
21736                            uri: params.text_document_position_params.text_document.uri,
21737                            range: lsp::Range::new(
21738                                lsp::Position::new(4, 3),
21739                                lsp::Position::new(4, 6),
21740                            ),
21741                        })))
21742                    }
21743                },
21744            );
21745        let references = cx
21746            .lsp
21747            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21748                Ok(Some(vec![lsp::Location {
21749                    uri: params.text_document_position.text_document.uri,
21750                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
21751                }]))
21752            });
21753        (go_to_definition, references)
21754    };
21755
21756    cx.set_state(
21757        &r#"fn one() {
21758            let mut a = ˇtwo();
21759        }
21760
21761        fn two() {}"#
21762            .unindent(),
21763    );
21764    set_up_lsp_handlers(false, &mut cx);
21765    let navigated = cx
21766        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21767        .await
21768        .expect("Failed to navigate to definition");
21769    assert_eq!(
21770        navigated,
21771        Navigated::Yes,
21772        "Should have navigated to definition from the GetDefinition response"
21773    );
21774    cx.assert_editor_state(
21775        &r#"fn one() {
21776            let mut a = two();
21777        }
21778
21779        fn «twoˇ»() {}"#
21780            .unindent(),
21781    );
21782
21783    let editors = cx.update_workspace(|workspace, _, cx| {
21784        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21785    });
21786    cx.update_editor(|_, _, test_editor_cx| {
21787        assert_eq!(
21788            editors.len(),
21789            1,
21790            "Initially, only one, test, editor should be open in the workspace"
21791        );
21792        assert_eq!(
21793            test_editor_cx.entity(),
21794            editors.last().expect("Asserted len is 1").clone()
21795        );
21796    });
21797
21798    set_up_lsp_handlers(true, &mut cx);
21799    let navigated = cx
21800        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21801        .await
21802        .expect("Failed to navigate to lookup references");
21803    assert_eq!(
21804        navigated,
21805        Navigated::Yes,
21806        "Should have navigated to references as a fallback after empty GoToDefinition response"
21807    );
21808    // We should not change the selections in the existing file,
21809    // if opening another milti buffer with the references
21810    cx.assert_editor_state(
21811        &r#"fn one() {
21812            let mut a = two();
21813        }
21814
21815        fn «twoˇ»() {}"#
21816            .unindent(),
21817    );
21818    let editors = cx.update_workspace(|workspace, _, cx| {
21819        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21820    });
21821    cx.update_editor(|_, _, test_editor_cx| {
21822        assert_eq!(
21823            editors.len(),
21824            2,
21825            "After falling back to references search, we open a new editor with the results"
21826        );
21827        let references_fallback_text = editors
21828            .into_iter()
21829            .find(|new_editor| *new_editor != test_editor_cx.entity())
21830            .expect("Should have one non-test editor now")
21831            .read(test_editor_cx)
21832            .text(test_editor_cx);
21833        assert_eq!(
21834            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
21835            "Should use the range from the references response and not the GoToDefinition one"
21836        );
21837    });
21838}
21839
21840#[gpui::test]
21841async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
21842    init_test(cx, |_| {});
21843    cx.update(|cx| {
21844        let mut editor_settings = EditorSettings::get_global(cx).clone();
21845        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
21846        EditorSettings::override_global(editor_settings, cx);
21847    });
21848    let mut cx = EditorLspTestContext::new_rust(
21849        lsp::ServerCapabilities {
21850            definition_provider: Some(lsp::OneOf::Left(true)),
21851            references_provider: Some(lsp::OneOf::Left(true)),
21852            ..lsp::ServerCapabilities::default()
21853        },
21854        cx,
21855    )
21856    .await;
21857    let original_state = r#"fn one() {
21858        let mut a = ˇtwo();
21859    }
21860
21861    fn two() {}"#
21862        .unindent();
21863    cx.set_state(&original_state);
21864
21865    let mut go_to_definition = cx
21866        .lsp
21867        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21868            move |_, _| async move { Ok(None) },
21869        );
21870    let _references = cx
21871        .lsp
21872        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
21873            panic!("Should not call for references with no go to definition fallback")
21874        });
21875
21876    let navigated = cx
21877        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21878        .await
21879        .expect("Failed to navigate to lookup references");
21880    go_to_definition
21881        .next()
21882        .await
21883        .expect("Should have called the go_to_definition handler");
21884
21885    assert_eq!(
21886        navigated,
21887        Navigated::No,
21888        "Should have navigated to references as a fallback after empty GoToDefinition response"
21889    );
21890    cx.assert_editor_state(&original_state);
21891    let editors = cx.update_workspace(|workspace, _, cx| {
21892        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21893    });
21894    cx.update_editor(|_, _, _| {
21895        assert_eq!(
21896            editors.len(),
21897            1,
21898            "After unsuccessful fallback, no other editor should have been opened"
21899        );
21900    });
21901}
21902
21903#[gpui::test]
21904async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
21905    init_test(cx, |_| {});
21906    let mut cx = EditorLspTestContext::new_rust(
21907        lsp::ServerCapabilities {
21908            references_provider: Some(lsp::OneOf::Left(true)),
21909            ..lsp::ServerCapabilities::default()
21910        },
21911        cx,
21912    )
21913    .await;
21914
21915    cx.set_state(
21916        &r#"
21917        fn one() {
21918            let mut a = two();
21919        }
21920
21921        fn ˇtwo() {}"#
21922            .unindent(),
21923    );
21924    cx.lsp
21925        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21926            Ok(Some(vec![
21927                lsp::Location {
21928                    uri: params.text_document_position.text_document.uri.clone(),
21929                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21930                },
21931                lsp::Location {
21932                    uri: params.text_document_position.text_document.uri,
21933                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
21934                },
21935            ]))
21936        });
21937    let navigated = cx
21938        .update_editor(|editor, window, cx| {
21939            editor.find_all_references(&FindAllReferences, window, cx)
21940        })
21941        .unwrap()
21942        .await
21943        .expect("Failed to navigate to references");
21944    assert_eq!(
21945        navigated,
21946        Navigated::Yes,
21947        "Should have navigated to references from the FindAllReferences response"
21948    );
21949    cx.assert_editor_state(
21950        &r#"fn one() {
21951            let mut a = two();
21952        }
21953
21954        fn ˇtwo() {}"#
21955            .unindent(),
21956    );
21957
21958    let editors = cx.update_workspace(|workspace, _, cx| {
21959        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21960    });
21961    cx.update_editor(|_, _, _| {
21962        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
21963    });
21964
21965    cx.set_state(
21966        &r#"fn one() {
21967            let mut a = ˇtwo();
21968        }
21969
21970        fn two() {}"#
21971            .unindent(),
21972    );
21973    let navigated = cx
21974        .update_editor(|editor, window, cx| {
21975            editor.find_all_references(&FindAllReferences, window, cx)
21976        })
21977        .unwrap()
21978        .await
21979        .expect("Failed to navigate to references");
21980    assert_eq!(
21981        navigated,
21982        Navigated::Yes,
21983        "Should have navigated to references from the FindAllReferences response"
21984    );
21985    cx.assert_editor_state(
21986        &r#"fn one() {
21987            let mut a = ˇtwo();
21988        }
21989
21990        fn two() {}"#
21991            .unindent(),
21992    );
21993    let editors = cx.update_workspace(|workspace, _, cx| {
21994        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21995    });
21996    cx.update_editor(|_, _, _| {
21997        assert_eq!(
21998            editors.len(),
21999            2,
22000            "should have re-used the previous multibuffer"
22001        );
22002    });
22003
22004    cx.set_state(
22005        &r#"fn one() {
22006            let mut a = ˇtwo();
22007        }
22008        fn three() {}
22009        fn two() {}"#
22010            .unindent(),
22011    );
22012    cx.lsp
22013        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
22014            Ok(Some(vec![
22015                lsp::Location {
22016                    uri: params.text_document_position.text_document.uri.clone(),
22017                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
22018                },
22019                lsp::Location {
22020                    uri: params.text_document_position.text_document.uri,
22021                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
22022                },
22023            ]))
22024        });
22025    let navigated = cx
22026        .update_editor(|editor, window, cx| {
22027            editor.find_all_references(&FindAllReferences, window, cx)
22028        })
22029        .unwrap()
22030        .await
22031        .expect("Failed to navigate to references");
22032    assert_eq!(
22033        navigated,
22034        Navigated::Yes,
22035        "Should have navigated to references from the FindAllReferences response"
22036    );
22037    cx.assert_editor_state(
22038        &r#"fn one() {
22039                let mut a = ˇtwo();
22040            }
22041            fn three() {}
22042            fn two() {}"#
22043            .unindent(),
22044    );
22045    let editors = cx.update_workspace(|workspace, _, cx| {
22046        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22047    });
22048    cx.update_editor(|_, _, _| {
22049        assert_eq!(
22050            editors.len(),
22051            3,
22052            "should have used a new multibuffer as offsets changed"
22053        );
22054    });
22055}
22056#[gpui::test]
22057async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
22058    init_test(cx, |_| {});
22059
22060    let language = Arc::new(Language::new(
22061        LanguageConfig::default(),
22062        Some(tree_sitter_rust::LANGUAGE.into()),
22063    ));
22064
22065    let text = r#"
22066        #[cfg(test)]
22067        mod tests() {
22068            #[test]
22069            fn runnable_1() {
22070                let a = 1;
22071            }
22072
22073            #[test]
22074            fn runnable_2() {
22075                let a = 1;
22076                let b = 2;
22077            }
22078        }
22079    "#
22080    .unindent();
22081
22082    let fs = FakeFs::new(cx.executor());
22083    fs.insert_file("/file.rs", Default::default()).await;
22084
22085    let project = Project::test(fs, ["/a".as_ref()], cx).await;
22086    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22087    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22088    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
22089    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
22090
22091    let editor = cx.new_window_entity(|window, cx| {
22092        Editor::new(
22093            EditorMode::full(),
22094            multi_buffer,
22095            Some(project.clone()),
22096            window,
22097            cx,
22098        )
22099    });
22100
22101    editor.update_in(cx, |editor, window, cx| {
22102        let snapshot = editor.buffer().read(cx).snapshot(cx);
22103        editor.tasks.insert(
22104            (buffer.read(cx).remote_id(), 3),
22105            RunnableTasks {
22106                templates: vec![],
22107                offset: snapshot.anchor_before(43),
22108                column: 0,
22109                extra_variables: HashMap::default(),
22110                context_range: BufferOffset(43)..BufferOffset(85),
22111            },
22112        );
22113        editor.tasks.insert(
22114            (buffer.read(cx).remote_id(), 8),
22115            RunnableTasks {
22116                templates: vec![],
22117                offset: snapshot.anchor_before(86),
22118                column: 0,
22119                extra_variables: HashMap::default(),
22120                context_range: BufferOffset(86)..BufferOffset(191),
22121            },
22122        );
22123
22124        // Test finding task when cursor is inside function body
22125        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22126            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
22127        });
22128        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
22129        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
22130
22131        // Test finding task when cursor is on function name
22132        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22133            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
22134        });
22135        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
22136        assert_eq!(row, 8, "Should find task when cursor is on function name");
22137    });
22138}
22139
22140#[gpui::test]
22141async fn test_folding_buffers(cx: &mut TestAppContext) {
22142    init_test(cx, |_| {});
22143
22144    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
22145    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
22146    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
22147
22148    let fs = FakeFs::new(cx.executor());
22149    fs.insert_tree(
22150        path!("/a"),
22151        json!({
22152            "first.rs": sample_text_1,
22153            "second.rs": sample_text_2,
22154            "third.rs": sample_text_3,
22155        }),
22156    )
22157    .await;
22158    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22159    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22160    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22161    let worktree = project.update(cx, |project, cx| {
22162        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22163        assert_eq!(worktrees.len(), 1);
22164        worktrees.pop().unwrap()
22165    });
22166    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22167
22168    let buffer_1 = project
22169        .update(cx, |project, cx| {
22170            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
22171        })
22172        .await
22173        .unwrap();
22174    let buffer_2 = project
22175        .update(cx, |project, cx| {
22176            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
22177        })
22178        .await
22179        .unwrap();
22180    let buffer_3 = project
22181        .update(cx, |project, cx| {
22182            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
22183        })
22184        .await
22185        .unwrap();
22186
22187    let multi_buffer = cx.new(|cx| {
22188        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22189        multi_buffer.push_excerpts(
22190            buffer_1.clone(),
22191            [
22192                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22193                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22194                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22195            ],
22196            cx,
22197        );
22198        multi_buffer.push_excerpts(
22199            buffer_2.clone(),
22200            [
22201                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22202                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22203                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22204            ],
22205            cx,
22206        );
22207        multi_buffer.push_excerpts(
22208            buffer_3.clone(),
22209            [
22210                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22211                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22212                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22213            ],
22214            cx,
22215        );
22216        multi_buffer
22217    });
22218    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22219        Editor::new(
22220            EditorMode::full(),
22221            multi_buffer.clone(),
22222            Some(project.clone()),
22223            window,
22224            cx,
22225        )
22226    });
22227
22228    assert_eq!(
22229        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22230        "\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",
22231    );
22232
22233    multi_buffer_editor.update(cx, |editor, cx| {
22234        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
22235    });
22236    assert_eq!(
22237        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22238        "\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",
22239        "After folding the first buffer, its text should not be displayed"
22240    );
22241
22242    multi_buffer_editor.update(cx, |editor, cx| {
22243        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
22244    });
22245    assert_eq!(
22246        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22247        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
22248        "After folding the second buffer, its text should not be displayed"
22249    );
22250
22251    multi_buffer_editor.update(cx, |editor, cx| {
22252        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
22253    });
22254    assert_eq!(
22255        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22256        "\n\n\n\n\n",
22257        "After folding the third buffer, its text should not be displayed"
22258    );
22259
22260    // Emulate selection inside the fold logic, that should work
22261    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22262        editor
22263            .snapshot(window, cx)
22264            .next_line_boundary(Point::new(0, 4));
22265    });
22266
22267    multi_buffer_editor.update(cx, |editor, cx| {
22268        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
22269    });
22270    assert_eq!(
22271        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22272        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
22273        "After unfolding the second buffer, its text should be displayed"
22274    );
22275
22276    // Typing inside of buffer 1 causes that buffer to be unfolded.
22277    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22278        assert_eq!(
22279            multi_buffer
22280                .read(cx)
22281                .snapshot(cx)
22282                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
22283                .collect::<String>(),
22284            "bbbb"
22285        );
22286        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22287            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
22288        });
22289        editor.handle_input("B", window, cx);
22290    });
22291
22292    assert_eq!(
22293        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22294        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
22295        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
22296    );
22297
22298    multi_buffer_editor.update(cx, |editor, cx| {
22299        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
22300    });
22301    assert_eq!(
22302        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22303        "\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",
22304        "After unfolding the all buffers, all original text should be displayed"
22305    );
22306}
22307
22308#[gpui::test]
22309async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
22310    init_test(cx, |_| {});
22311
22312    let sample_text_1 = "1111\n2222\n3333".to_string();
22313    let sample_text_2 = "4444\n5555\n6666".to_string();
22314    let sample_text_3 = "7777\n8888\n9999".to_string();
22315
22316    let fs = FakeFs::new(cx.executor());
22317    fs.insert_tree(
22318        path!("/a"),
22319        json!({
22320            "first.rs": sample_text_1,
22321            "second.rs": sample_text_2,
22322            "third.rs": sample_text_3,
22323        }),
22324    )
22325    .await;
22326    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22327    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22328    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22329    let worktree = project.update(cx, |project, cx| {
22330        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22331        assert_eq!(worktrees.len(), 1);
22332        worktrees.pop().unwrap()
22333    });
22334    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22335
22336    let buffer_1 = project
22337        .update(cx, |project, cx| {
22338            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
22339        })
22340        .await
22341        .unwrap();
22342    let buffer_2 = project
22343        .update(cx, |project, cx| {
22344            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
22345        })
22346        .await
22347        .unwrap();
22348    let buffer_3 = project
22349        .update(cx, |project, cx| {
22350            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
22351        })
22352        .await
22353        .unwrap();
22354
22355    let multi_buffer = cx.new(|cx| {
22356        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22357        multi_buffer.push_excerpts(
22358            buffer_1.clone(),
22359            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22360            cx,
22361        );
22362        multi_buffer.push_excerpts(
22363            buffer_2.clone(),
22364            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22365            cx,
22366        );
22367        multi_buffer.push_excerpts(
22368            buffer_3.clone(),
22369            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22370            cx,
22371        );
22372        multi_buffer
22373    });
22374
22375    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22376        Editor::new(
22377            EditorMode::full(),
22378            multi_buffer,
22379            Some(project.clone()),
22380            window,
22381            cx,
22382        )
22383    });
22384
22385    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
22386    assert_eq!(
22387        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22388        full_text,
22389    );
22390
22391    multi_buffer_editor.update(cx, |editor, cx| {
22392        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
22393    });
22394    assert_eq!(
22395        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22396        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
22397        "After folding the first buffer, its text should not be displayed"
22398    );
22399
22400    multi_buffer_editor.update(cx, |editor, cx| {
22401        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
22402    });
22403
22404    assert_eq!(
22405        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22406        "\n\n\n\n\n\n7777\n8888\n9999",
22407        "After folding the second buffer, its text should not be displayed"
22408    );
22409
22410    multi_buffer_editor.update(cx, |editor, cx| {
22411        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
22412    });
22413    assert_eq!(
22414        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22415        "\n\n\n\n\n",
22416        "After folding the third buffer, its text should not be displayed"
22417    );
22418
22419    multi_buffer_editor.update(cx, |editor, cx| {
22420        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
22421    });
22422    assert_eq!(
22423        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22424        "\n\n\n\n4444\n5555\n6666\n\n",
22425        "After unfolding the second buffer, its text should be displayed"
22426    );
22427
22428    multi_buffer_editor.update(cx, |editor, cx| {
22429        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
22430    });
22431    assert_eq!(
22432        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22433        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
22434        "After unfolding the first buffer, its text should be displayed"
22435    );
22436
22437    multi_buffer_editor.update(cx, |editor, cx| {
22438        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
22439    });
22440    assert_eq!(
22441        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22442        full_text,
22443        "After unfolding all buffers, all original text should be displayed"
22444    );
22445}
22446
22447#[gpui::test]
22448async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
22449    init_test(cx, |_| {});
22450
22451    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
22452
22453    let fs = FakeFs::new(cx.executor());
22454    fs.insert_tree(
22455        path!("/a"),
22456        json!({
22457            "main.rs": sample_text,
22458        }),
22459    )
22460    .await;
22461    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22462    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22463    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22464    let worktree = project.update(cx, |project, cx| {
22465        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22466        assert_eq!(worktrees.len(), 1);
22467        worktrees.pop().unwrap()
22468    });
22469    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22470
22471    let buffer_1 = project
22472        .update(cx, |project, cx| {
22473            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22474        })
22475        .await
22476        .unwrap();
22477
22478    let multi_buffer = cx.new(|cx| {
22479        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22480        multi_buffer.push_excerpts(
22481            buffer_1.clone(),
22482            [ExcerptRange::new(
22483                Point::new(0, 0)
22484                    ..Point::new(
22485                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
22486                        0,
22487                    ),
22488            )],
22489            cx,
22490        );
22491        multi_buffer
22492    });
22493    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22494        Editor::new(
22495            EditorMode::full(),
22496            multi_buffer,
22497            Some(project.clone()),
22498            window,
22499            cx,
22500        )
22501    });
22502
22503    let selection_range = Point::new(1, 0)..Point::new(2, 0);
22504    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22505        enum TestHighlight {}
22506        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
22507        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
22508        editor.highlight_text::<TestHighlight>(
22509            vec![highlight_range.clone()],
22510            HighlightStyle::color(Hsla::green()),
22511            cx,
22512        );
22513        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22514            s.select_ranges(Some(highlight_range))
22515        });
22516    });
22517
22518    let full_text = format!("\n\n{sample_text}");
22519    assert_eq!(
22520        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22521        full_text,
22522    );
22523}
22524
22525#[gpui::test]
22526async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
22527    init_test(cx, |_| {});
22528    cx.update(|cx| {
22529        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
22530            "keymaps/default-linux.json",
22531            cx,
22532        )
22533        .unwrap();
22534        cx.bind_keys(default_key_bindings);
22535    });
22536
22537    let (editor, cx) = cx.add_window_view(|window, cx| {
22538        let multi_buffer = MultiBuffer::build_multi(
22539            [
22540                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
22541                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
22542                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
22543                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
22544            ],
22545            cx,
22546        );
22547        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
22548
22549        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
22550        // fold all but the second buffer, so that we test navigating between two
22551        // adjacent folded buffers, as well as folded buffers at the start and
22552        // end the multibuffer
22553        editor.fold_buffer(buffer_ids[0], cx);
22554        editor.fold_buffer(buffer_ids[2], cx);
22555        editor.fold_buffer(buffer_ids[3], cx);
22556
22557        editor
22558    });
22559    cx.simulate_resize(size(px(1000.), px(1000.)));
22560
22561    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
22562    cx.assert_excerpts_with_selections(indoc! {"
22563        [EXCERPT]
22564        ˇ[FOLDED]
22565        [EXCERPT]
22566        a1
22567        b1
22568        [EXCERPT]
22569        [FOLDED]
22570        [EXCERPT]
22571        [FOLDED]
22572        "
22573    });
22574    cx.simulate_keystroke("down");
22575    cx.assert_excerpts_with_selections(indoc! {"
22576        [EXCERPT]
22577        [FOLDED]
22578        [EXCERPT]
22579        ˇa1
22580        b1
22581        [EXCERPT]
22582        [FOLDED]
22583        [EXCERPT]
22584        [FOLDED]
22585        "
22586    });
22587    cx.simulate_keystroke("down");
22588    cx.assert_excerpts_with_selections(indoc! {"
22589        [EXCERPT]
22590        [FOLDED]
22591        [EXCERPT]
22592        a1
22593        ˇb1
22594        [EXCERPT]
22595        [FOLDED]
22596        [EXCERPT]
22597        [FOLDED]
22598        "
22599    });
22600    cx.simulate_keystroke("down");
22601    cx.assert_excerpts_with_selections(indoc! {"
22602        [EXCERPT]
22603        [FOLDED]
22604        [EXCERPT]
22605        a1
22606        b1
22607        ˇ[EXCERPT]
22608        [FOLDED]
22609        [EXCERPT]
22610        [FOLDED]
22611        "
22612    });
22613    cx.simulate_keystroke("down");
22614    cx.assert_excerpts_with_selections(indoc! {"
22615        [EXCERPT]
22616        [FOLDED]
22617        [EXCERPT]
22618        a1
22619        b1
22620        [EXCERPT]
22621        ˇ[FOLDED]
22622        [EXCERPT]
22623        [FOLDED]
22624        "
22625    });
22626    for _ in 0..5 {
22627        cx.simulate_keystroke("down");
22628        cx.assert_excerpts_with_selections(indoc! {"
22629            [EXCERPT]
22630            [FOLDED]
22631            [EXCERPT]
22632            a1
22633            b1
22634            [EXCERPT]
22635            [FOLDED]
22636            [EXCERPT]
22637            ˇ[FOLDED]
22638            "
22639        });
22640    }
22641
22642    cx.simulate_keystroke("up");
22643    cx.assert_excerpts_with_selections(indoc! {"
22644        [EXCERPT]
22645        [FOLDED]
22646        [EXCERPT]
22647        a1
22648        b1
22649        [EXCERPT]
22650        ˇ[FOLDED]
22651        [EXCERPT]
22652        [FOLDED]
22653        "
22654    });
22655    cx.simulate_keystroke("up");
22656    cx.assert_excerpts_with_selections(indoc! {"
22657        [EXCERPT]
22658        [FOLDED]
22659        [EXCERPT]
22660        a1
22661        b1
22662        ˇ[EXCERPT]
22663        [FOLDED]
22664        [EXCERPT]
22665        [FOLDED]
22666        "
22667    });
22668    cx.simulate_keystroke("up");
22669    cx.assert_excerpts_with_selections(indoc! {"
22670        [EXCERPT]
22671        [FOLDED]
22672        [EXCERPT]
22673        a1
22674        ˇb1
22675        [EXCERPT]
22676        [FOLDED]
22677        [EXCERPT]
22678        [FOLDED]
22679        "
22680    });
22681    cx.simulate_keystroke("up");
22682    cx.assert_excerpts_with_selections(indoc! {"
22683        [EXCERPT]
22684        [FOLDED]
22685        [EXCERPT]
22686        ˇa1
22687        b1
22688        [EXCERPT]
22689        [FOLDED]
22690        [EXCERPT]
22691        [FOLDED]
22692        "
22693    });
22694    for _ in 0..5 {
22695        cx.simulate_keystroke("up");
22696        cx.assert_excerpts_with_selections(indoc! {"
22697            [EXCERPT]
22698            ˇ[FOLDED]
22699            [EXCERPT]
22700            a1
22701            b1
22702            [EXCERPT]
22703            [FOLDED]
22704            [EXCERPT]
22705            [FOLDED]
22706            "
22707        });
22708    }
22709}
22710
22711#[gpui::test]
22712async fn test_edit_prediction_text(cx: &mut TestAppContext) {
22713    init_test(cx, |_| {});
22714
22715    // Simple insertion
22716    assert_highlighted_edits(
22717        "Hello, world!",
22718        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
22719        true,
22720        cx,
22721        |highlighted_edits, cx| {
22722            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
22723            assert_eq!(highlighted_edits.highlights.len(), 1);
22724            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
22725            assert_eq!(
22726                highlighted_edits.highlights[0].1.background_color,
22727                Some(cx.theme().status().created_background)
22728            );
22729        },
22730    )
22731    .await;
22732
22733    // Replacement
22734    assert_highlighted_edits(
22735        "This is a test.",
22736        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
22737        false,
22738        cx,
22739        |highlighted_edits, cx| {
22740            assert_eq!(highlighted_edits.text, "That is a test.");
22741            assert_eq!(highlighted_edits.highlights.len(), 1);
22742            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
22743            assert_eq!(
22744                highlighted_edits.highlights[0].1.background_color,
22745                Some(cx.theme().status().created_background)
22746            );
22747        },
22748    )
22749    .await;
22750
22751    // Multiple edits
22752    assert_highlighted_edits(
22753        "Hello, world!",
22754        vec![
22755            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
22756            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
22757        ],
22758        false,
22759        cx,
22760        |highlighted_edits, cx| {
22761            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
22762            assert_eq!(highlighted_edits.highlights.len(), 2);
22763            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
22764            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
22765            assert_eq!(
22766                highlighted_edits.highlights[0].1.background_color,
22767                Some(cx.theme().status().created_background)
22768            );
22769            assert_eq!(
22770                highlighted_edits.highlights[1].1.background_color,
22771                Some(cx.theme().status().created_background)
22772            );
22773        },
22774    )
22775    .await;
22776
22777    // Multiple lines with edits
22778    assert_highlighted_edits(
22779        "First line\nSecond line\nThird line\nFourth line",
22780        vec![
22781            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
22782            (
22783                Point::new(2, 0)..Point::new(2, 10),
22784                "New third line".to_string(),
22785            ),
22786            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
22787        ],
22788        false,
22789        cx,
22790        |highlighted_edits, cx| {
22791            assert_eq!(
22792                highlighted_edits.text,
22793                "Second modified\nNew third line\nFourth updated line"
22794            );
22795            assert_eq!(highlighted_edits.highlights.len(), 3);
22796            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
22797            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
22798            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
22799            for highlight in &highlighted_edits.highlights {
22800                assert_eq!(
22801                    highlight.1.background_color,
22802                    Some(cx.theme().status().created_background)
22803                );
22804            }
22805        },
22806    )
22807    .await;
22808}
22809
22810#[gpui::test]
22811async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
22812    init_test(cx, |_| {});
22813
22814    // Deletion
22815    assert_highlighted_edits(
22816        "Hello, world!",
22817        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
22818        true,
22819        cx,
22820        |highlighted_edits, cx| {
22821            assert_eq!(highlighted_edits.text, "Hello, world!");
22822            assert_eq!(highlighted_edits.highlights.len(), 1);
22823            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
22824            assert_eq!(
22825                highlighted_edits.highlights[0].1.background_color,
22826                Some(cx.theme().status().deleted_background)
22827            );
22828        },
22829    )
22830    .await;
22831
22832    // Insertion
22833    assert_highlighted_edits(
22834        "Hello, world!",
22835        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
22836        true,
22837        cx,
22838        |highlighted_edits, cx| {
22839            assert_eq!(highlighted_edits.highlights.len(), 1);
22840            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
22841            assert_eq!(
22842                highlighted_edits.highlights[0].1.background_color,
22843                Some(cx.theme().status().created_background)
22844            );
22845        },
22846    )
22847    .await;
22848}
22849
22850async fn assert_highlighted_edits(
22851    text: &str,
22852    edits: Vec<(Range<Point>, String)>,
22853    include_deletions: bool,
22854    cx: &mut TestAppContext,
22855    assertion_fn: impl Fn(HighlightedText, &App),
22856) {
22857    let window = cx.add_window(|window, cx| {
22858        let buffer = MultiBuffer::build_simple(text, cx);
22859        Editor::new(EditorMode::full(), buffer, None, window, cx)
22860    });
22861    let cx = &mut VisualTestContext::from_window(*window, cx);
22862
22863    let (buffer, snapshot) = window
22864        .update(cx, |editor, _window, cx| {
22865            (
22866                editor.buffer().clone(),
22867                editor.buffer().read(cx).snapshot(cx),
22868            )
22869        })
22870        .unwrap();
22871
22872    let edits = edits
22873        .into_iter()
22874        .map(|(range, edit)| {
22875            (
22876                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
22877                edit,
22878            )
22879        })
22880        .collect::<Vec<_>>();
22881
22882    let text_anchor_edits = edits
22883        .clone()
22884        .into_iter()
22885        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit.into()))
22886        .collect::<Vec<_>>();
22887
22888    let edit_preview = window
22889        .update(cx, |_, _window, cx| {
22890            buffer
22891                .read(cx)
22892                .as_singleton()
22893                .unwrap()
22894                .read(cx)
22895                .preview_edits(text_anchor_edits.into(), cx)
22896        })
22897        .unwrap()
22898        .await;
22899
22900    cx.update(|_window, cx| {
22901        let highlighted_edits = edit_prediction_edit_text(
22902            snapshot.as_singleton().unwrap().2,
22903            &edits,
22904            &edit_preview,
22905            include_deletions,
22906            cx,
22907        );
22908        assertion_fn(highlighted_edits, cx)
22909    });
22910}
22911
22912#[track_caller]
22913fn assert_breakpoint(
22914    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
22915    path: &Arc<Path>,
22916    expected: Vec<(u32, Breakpoint)>,
22917) {
22918    if expected.is_empty() {
22919        assert!(!breakpoints.contains_key(path), "{}", path.display());
22920    } else {
22921        let mut breakpoint = breakpoints
22922            .get(path)
22923            .unwrap()
22924            .iter()
22925            .map(|breakpoint| {
22926                (
22927                    breakpoint.row,
22928                    Breakpoint {
22929                        message: breakpoint.message.clone(),
22930                        state: breakpoint.state,
22931                        condition: breakpoint.condition.clone(),
22932                        hit_condition: breakpoint.hit_condition.clone(),
22933                    },
22934                )
22935            })
22936            .collect::<Vec<_>>();
22937
22938        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
22939
22940        assert_eq!(expected, breakpoint);
22941    }
22942}
22943
22944fn add_log_breakpoint_at_cursor(
22945    editor: &mut Editor,
22946    log_message: &str,
22947    window: &mut Window,
22948    cx: &mut Context<Editor>,
22949) {
22950    let (anchor, bp) = editor
22951        .breakpoints_at_cursors(window, cx)
22952        .first()
22953        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
22954        .unwrap_or_else(|| {
22955            let snapshot = editor.snapshot(window, cx);
22956            let cursor_position: Point =
22957                editor.selections.newest(&snapshot.display_snapshot).head();
22958
22959            let breakpoint_position = snapshot
22960                .buffer_snapshot()
22961                .anchor_before(Point::new(cursor_position.row, 0));
22962
22963            (breakpoint_position, Breakpoint::new_log(log_message))
22964        });
22965
22966    editor.edit_breakpoint_at_anchor(
22967        anchor,
22968        bp,
22969        BreakpointEditAction::EditLogMessage(log_message.into()),
22970        cx,
22971    );
22972}
22973
22974#[gpui::test]
22975async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
22976    init_test(cx, |_| {});
22977
22978    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22979    let fs = FakeFs::new(cx.executor());
22980    fs.insert_tree(
22981        path!("/a"),
22982        json!({
22983            "main.rs": sample_text,
22984        }),
22985    )
22986    .await;
22987    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22988    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22989    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22990
22991    let fs = FakeFs::new(cx.executor());
22992    fs.insert_tree(
22993        path!("/a"),
22994        json!({
22995            "main.rs": sample_text,
22996        }),
22997    )
22998    .await;
22999    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23000    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23001    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23002    let worktree_id = workspace
23003        .update(cx, |workspace, _window, cx| {
23004            workspace.project().update(cx, |project, cx| {
23005                project.worktrees(cx).next().unwrap().read(cx).id()
23006            })
23007        })
23008        .unwrap();
23009
23010    let buffer = project
23011        .update(cx, |project, cx| {
23012            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23013        })
23014        .await
23015        .unwrap();
23016
23017    let (editor, cx) = cx.add_window_view(|window, cx| {
23018        Editor::new(
23019            EditorMode::full(),
23020            MultiBuffer::build_from_buffer(buffer, cx),
23021            Some(project.clone()),
23022            window,
23023            cx,
23024        )
23025    });
23026
23027    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23028    let abs_path = project.read_with(cx, |project, cx| {
23029        project
23030            .absolute_path(&project_path, cx)
23031            .map(Arc::from)
23032            .unwrap()
23033    });
23034
23035    // assert we can add breakpoint on the first line
23036    editor.update_in(cx, |editor, window, cx| {
23037        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23038        editor.move_to_end(&MoveToEnd, window, cx);
23039        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23040    });
23041
23042    let breakpoints = editor.update(cx, |editor, cx| {
23043        editor
23044            .breakpoint_store()
23045            .as_ref()
23046            .unwrap()
23047            .read(cx)
23048            .all_source_breakpoints(cx)
23049    });
23050
23051    assert_eq!(1, breakpoints.len());
23052    assert_breakpoint(
23053        &breakpoints,
23054        &abs_path,
23055        vec![
23056            (0, Breakpoint::new_standard()),
23057            (3, Breakpoint::new_standard()),
23058        ],
23059    );
23060
23061    editor.update_in(cx, |editor, window, cx| {
23062        editor.move_to_beginning(&MoveToBeginning, window, cx);
23063        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23064    });
23065
23066    let breakpoints = editor.update(cx, |editor, cx| {
23067        editor
23068            .breakpoint_store()
23069            .as_ref()
23070            .unwrap()
23071            .read(cx)
23072            .all_source_breakpoints(cx)
23073    });
23074
23075    assert_eq!(1, breakpoints.len());
23076    assert_breakpoint(
23077        &breakpoints,
23078        &abs_path,
23079        vec![(3, Breakpoint::new_standard())],
23080    );
23081
23082    editor.update_in(cx, |editor, window, cx| {
23083        editor.move_to_end(&MoveToEnd, window, cx);
23084        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23085    });
23086
23087    let breakpoints = editor.update(cx, |editor, cx| {
23088        editor
23089            .breakpoint_store()
23090            .as_ref()
23091            .unwrap()
23092            .read(cx)
23093            .all_source_breakpoints(cx)
23094    });
23095
23096    assert_eq!(0, breakpoints.len());
23097    assert_breakpoint(&breakpoints, &abs_path, vec![]);
23098}
23099
23100#[gpui::test]
23101async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
23102    init_test(cx, |_| {});
23103
23104    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23105
23106    let fs = FakeFs::new(cx.executor());
23107    fs.insert_tree(
23108        path!("/a"),
23109        json!({
23110            "main.rs": sample_text,
23111        }),
23112    )
23113    .await;
23114    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23115    let (workspace, cx) =
23116        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23117
23118    let worktree_id = workspace.update(cx, |workspace, cx| {
23119        workspace.project().update(cx, |project, cx| {
23120            project.worktrees(cx).next().unwrap().read(cx).id()
23121        })
23122    });
23123
23124    let buffer = project
23125        .update(cx, |project, cx| {
23126            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23127        })
23128        .await
23129        .unwrap();
23130
23131    let (editor, cx) = cx.add_window_view(|window, cx| {
23132        Editor::new(
23133            EditorMode::full(),
23134            MultiBuffer::build_from_buffer(buffer, cx),
23135            Some(project.clone()),
23136            window,
23137            cx,
23138        )
23139    });
23140
23141    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23142    let abs_path = project.read_with(cx, |project, cx| {
23143        project
23144            .absolute_path(&project_path, cx)
23145            .map(Arc::from)
23146            .unwrap()
23147    });
23148
23149    editor.update_in(cx, |editor, window, cx| {
23150        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
23151    });
23152
23153    let breakpoints = editor.update(cx, |editor, cx| {
23154        editor
23155            .breakpoint_store()
23156            .as_ref()
23157            .unwrap()
23158            .read(cx)
23159            .all_source_breakpoints(cx)
23160    });
23161
23162    assert_breakpoint(
23163        &breakpoints,
23164        &abs_path,
23165        vec![(0, Breakpoint::new_log("hello world"))],
23166    );
23167
23168    // Removing a log message from a log breakpoint should remove it
23169    editor.update_in(cx, |editor, window, cx| {
23170        add_log_breakpoint_at_cursor(editor, "", window, cx);
23171    });
23172
23173    let breakpoints = editor.update(cx, |editor, cx| {
23174        editor
23175            .breakpoint_store()
23176            .as_ref()
23177            .unwrap()
23178            .read(cx)
23179            .all_source_breakpoints(cx)
23180    });
23181
23182    assert_breakpoint(&breakpoints, &abs_path, vec![]);
23183
23184    editor.update_in(cx, |editor, window, cx| {
23185        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23186        editor.move_to_end(&MoveToEnd, window, cx);
23187        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23188        // Not adding a log message to a standard breakpoint shouldn't remove it
23189        add_log_breakpoint_at_cursor(editor, "", window, cx);
23190    });
23191
23192    let breakpoints = editor.update(cx, |editor, cx| {
23193        editor
23194            .breakpoint_store()
23195            .as_ref()
23196            .unwrap()
23197            .read(cx)
23198            .all_source_breakpoints(cx)
23199    });
23200
23201    assert_breakpoint(
23202        &breakpoints,
23203        &abs_path,
23204        vec![
23205            (0, Breakpoint::new_standard()),
23206            (3, Breakpoint::new_standard()),
23207        ],
23208    );
23209
23210    editor.update_in(cx, |editor, window, cx| {
23211        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
23212    });
23213
23214    let breakpoints = editor.update(cx, |editor, cx| {
23215        editor
23216            .breakpoint_store()
23217            .as_ref()
23218            .unwrap()
23219            .read(cx)
23220            .all_source_breakpoints(cx)
23221    });
23222
23223    assert_breakpoint(
23224        &breakpoints,
23225        &abs_path,
23226        vec![
23227            (0, Breakpoint::new_standard()),
23228            (3, Breakpoint::new_log("hello world")),
23229        ],
23230    );
23231
23232    editor.update_in(cx, |editor, window, cx| {
23233        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
23234    });
23235
23236    let breakpoints = editor.update(cx, |editor, cx| {
23237        editor
23238            .breakpoint_store()
23239            .as_ref()
23240            .unwrap()
23241            .read(cx)
23242            .all_source_breakpoints(cx)
23243    });
23244
23245    assert_breakpoint(
23246        &breakpoints,
23247        &abs_path,
23248        vec![
23249            (0, Breakpoint::new_standard()),
23250            (3, Breakpoint::new_log("hello Earth!!")),
23251        ],
23252    );
23253}
23254
23255/// This also tests that Editor::breakpoint_at_cursor_head is working properly
23256/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
23257/// or when breakpoints were placed out of order. This tests for a regression too
23258#[gpui::test]
23259async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
23260    init_test(cx, |_| {});
23261
23262    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23263    let fs = FakeFs::new(cx.executor());
23264    fs.insert_tree(
23265        path!("/a"),
23266        json!({
23267            "main.rs": sample_text,
23268        }),
23269    )
23270    .await;
23271    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23272    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23273    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23274
23275    let fs = FakeFs::new(cx.executor());
23276    fs.insert_tree(
23277        path!("/a"),
23278        json!({
23279            "main.rs": sample_text,
23280        }),
23281    )
23282    .await;
23283    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23284    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23285    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23286    let worktree_id = workspace
23287        .update(cx, |workspace, _window, cx| {
23288            workspace.project().update(cx, |project, cx| {
23289                project.worktrees(cx).next().unwrap().read(cx).id()
23290            })
23291        })
23292        .unwrap();
23293
23294    let buffer = project
23295        .update(cx, |project, cx| {
23296            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23297        })
23298        .await
23299        .unwrap();
23300
23301    let (editor, cx) = cx.add_window_view(|window, cx| {
23302        Editor::new(
23303            EditorMode::full(),
23304            MultiBuffer::build_from_buffer(buffer, cx),
23305            Some(project.clone()),
23306            window,
23307            cx,
23308        )
23309    });
23310
23311    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23312    let abs_path = project.read_with(cx, |project, cx| {
23313        project
23314            .absolute_path(&project_path, cx)
23315            .map(Arc::from)
23316            .unwrap()
23317    });
23318
23319    // assert we can add breakpoint on the first line
23320    editor.update_in(cx, |editor, window, cx| {
23321        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23322        editor.move_to_end(&MoveToEnd, window, cx);
23323        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23324        editor.move_up(&MoveUp, window, cx);
23325        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23326    });
23327
23328    let breakpoints = editor.update(cx, |editor, cx| {
23329        editor
23330            .breakpoint_store()
23331            .as_ref()
23332            .unwrap()
23333            .read(cx)
23334            .all_source_breakpoints(cx)
23335    });
23336
23337    assert_eq!(1, breakpoints.len());
23338    assert_breakpoint(
23339        &breakpoints,
23340        &abs_path,
23341        vec![
23342            (0, Breakpoint::new_standard()),
23343            (2, Breakpoint::new_standard()),
23344            (3, Breakpoint::new_standard()),
23345        ],
23346    );
23347
23348    editor.update_in(cx, |editor, window, cx| {
23349        editor.move_to_beginning(&MoveToBeginning, window, cx);
23350        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23351        editor.move_to_end(&MoveToEnd, window, cx);
23352        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23353        // Disabling a breakpoint that doesn't exist should do nothing
23354        editor.move_up(&MoveUp, window, cx);
23355        editor.move_up(&MoveUp, window, cx);
23356        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23357    });
23358
23359    let breakpoints = editor.update(cx, |editor, cx| {
23360        editor
23361            .breakpoint_store()
23362            .as_ref()
23363            .unwrap()
23364            .read(cx)
23365            .all_source_breakpoints(cx)
23366    });
23367
23368    let disable_breakpoint = {
23369        let mut bp = Breakpoint::new_standard();
23370        bp.state = BreakpointState::Disabled;
23371        bp
23372    };
23373
23374    assert_eq!(1, breakpoints.len());
23375    assert_breakpoint(
23376        &breakpoints,
23377        &abs_path,
23378        vec![
23379            (0, disable_breakpoint.clone()),
23380            (2, Breakpoint::new_standard()),
23381            (3, disable_breakpoint.clone()),
23382        ],
23383    );
23384
23385    editor.update_in(cx, |editor, window, cx| {
23386        editor.move_to_beginning(&MoveToBeginning, window, cx);
23387        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23388        editor.move_to_end(&MoveToEnd, window, cx);
23389        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23390        editor.move_up(&MoveUp, window, cx);
23391        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23392    });
23393
23394    let breakpoints = editor.update(cx, |editor, cx| {
23395        editor
23396            .breakpoint_store()
23397            .as_ref()
23398            .unwrap()
23399            .read(cx)
23400            .all_source_breakpoints(cx)
23401    });
23402
23403    assert_eq!(1, breakpoints.len());
23404    assert_breakpoint(
23405        &breakpoints,
23406        &abs_path,
23407        vec![
23408            (0, Breakpoint::new_standard()),
23409            (2, disable_breakpoint),
23410            (3, Breakpoint::new_standard()),
23411        ],
23412    );
23413}
23414
23415#[gpui::test]
23416async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
23417    init_test(cx, |_| {});
23418    let capabilities = lsp::ServerCapabilities {
23419        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
23420            prepare_provider: Some(true),
23421            work_done_progress_options: Default::default(),
23422        })),
23423        ..Default::default()
23424    };
23425    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23426
23427    cx.set_state(indoc! {"
23428        struct Fˇoo {}
23429    "});
23430
23431    cx.update_editor(|editor, _, cx| {
23432        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23433        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23434        editor.highlight_background::<DocumentHighlightRead>(
23435            &[highlight_range],
23436            |theme| theme.colors().editor_document_highlight_read_background,
23437            cx,
23438        );
23439    });
23440
23441    let mut prepare_rename_handler = cx
23442        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
23443            move |_, _, _| async move {
23444                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
23445                    start: lsp::Position {
23446                        line: 0,
23447                        character: 7,
23448                    },
23449                    end: lsp::Position {
23450                        line: 0,
23451                        character: 10,
23452                    },
23453                })))
23454            },
23455        );
23456    let prepare_rename_task = cx
23457        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23458        .expect("Prepare rename was not started");
23459    prepare_rename_handler.next().await.unwrap();
23460    prepare_rename_task.await.expect("Prepare rename failed");
23461
23462    let mut rename_handler =
23463        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23464            let edit = lsp::TextEdit {
23465                range: lsp::Range {
23466                    start: lsp::Position {
23467                        line: 0,
23468                        character: 7,
23469                    },
23470                    end: lsp::Position {
23471                        line: 0,
23472                        character: 10,
23473                    },
23474                },
23475                new_text: "FooRenamed".to_string(),
23476            };
23477            Ok(Some(lsp::WorkspaceEdit::new(
23478                // Specify the same edit twice
23479                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
23480            )))
23481        });
23482    let rename_task = cx
23483        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23484        .expect("Confirm rename was not started");
23485    rename_handler.next().await.unwrap();
23486    rename_task.await.expect("Confirm rename failed");
23487    cx.run_until_parked();
23488
23489    // Despite two edits, only one is actually applied as those are identical
23490    cx.assert_editor_state(indoc! {"
23491        struct FooRenamedˇ {}
23492    "});
23493}
23494
23495#[gpui::test]
23496async fn test_rename_without_prepare(cx: &mut TestAppContext) {
23497    init_test(cx, |_| {});
23498    // These capabilities indicate that the server does not support prepare rename.
23499    let capabilities = lsp::ServerCapabilities {
23500        rename_provider: Some(lsp::OneOf::Left(true)),
23501        ..Default::default()
23502    };
23503    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23504
23505    cx.set_state(indoc! {"
23506        struct Fˇoo {}
23507    "});
23508
23509    cx.update_editor(|editor, _window, cx| {
23510        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23511        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23512        editor.highlight_background::<DocumentHighlightRead>(
23513            &[highlight_range],
23514            |theme| theme.colors().editor_document_highlight_read_background,
23515            cx,
23516        );
23517    });
23518
23519    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23520        .expect("Prepare rename was not started")
23521        .await
23522        .expect("Prepare rename failed");
23523
23524    let mut rename_handler =
23525        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23526            let edit = lsp::TextEdit {
23527                range: lsp::Range {
23528                    start: lsp::Position {
23529                        line: 0,
23530                        character: 7,
23531                    },
23532                    end: lsp::Position {
23533                        line: 0,
23534                        character: 10,
23535                    },
23536                },
23537                new_text: "FooRenamed".to_string(),
23538            };
23539            Ok(Some(lsp::WorkspaceEdit::new(
23540                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
23541            )))
23542        });
23543    let rename_task = cx
23544        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23545        .expect("Confirm rename was not started");
23546    rename_handler.next().await.unwrap();
23547    rename_task.await.expect("Confirm rename failed");
23548    cx.run_until_parked();
23549
23550    // Correct range is renamed, as `surrounding_word` is used to find it.
23551    cx.assert_editor_state(indoc! {"
23552        struct FooRenamedˇ {}
23553    "});
23554}
23555
23556#[gpui::test]
23557async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
23558    init_test(cx, |_| {});
23559    let mut cx = EditorTestContext::new(cx).await;
23560
23561    let language = Arc::new(
23562        Language::new(
23563            LanguageConfig::default(),
23564            Some(tree_sitter_html::LANGUAGE.into()),
23565        )
23566        .with_brackets_query(
23567            r#"
23568            ("<" @open "/>" @close)
23569            ("</" @open ">" @close)
23570            ("<" @open ">" @close)
23571            ("\"" @open "\"" @close)
23572            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
23573        "#,
23574        )
23575        .unwrap(),
23576    );
23577    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23578
23579    cx.set_state(indoc! {"
23580        <span>ˇ</span>
23581    "});
23582    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23583    cx.assert_editor_state(indoc! {"
23584        <span>
23585        ˇ
23586        </span>
23587    "});
23588
23589    cx.set_state(indoc! {"
23590        <span><span></span>ˇ</span>
23591    "});
23592    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23593    cx.assert_editor_state(indoc! {"
23594        <span><span></span>
23595        ˇ</span>
23596    "});
23597
23598    cx.set_state(indoc! {"
23599        <span>ˇ
23600        </span>
23601    "});
23602    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23603    cx.assert_editor_state(indoc! {"
23604        <span>
23605        ˇ
23606        </span>
23607    "});
23608}
23609
23610#[gpui::test(iterations = 10)]
23611async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
23612    init_test(cx, |_| {});
23613
23614    let fs = FakeFs::new(cx.executor());
23615    fs.insert_tree(
23616        path!("/dir"),
23617        json!({
23618            "a.ts": "a",
23619        }),
23620    )
23621    .await;
23622
23623    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
23624    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23625    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23626
23627    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23628    language_registry.add(Arc::new(Language::new(
23629        LanguageConfig {
23630            name: "TypeScript".into(),
23631            matcher: LanguageMatcher {
23632                path_suffixes: vec!["ts".to_string()],
23633                ..Default::default()
23634            },
23635            ..Default::default()
23636        },
23637        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
23638    )));
23639    let mut fake_language_servers = language_registry.register_fake_lsp(
23640        "TypeScript",
23641        FakeLspAdapter {
23642            capabilities: lsp::ServerCapabilities {
23643                code_lens_provider: Some(lsp::CodeLensOptions {
23644                    resolve_provider: Some(true),
23645                }),
23646                execute_command_provider: Some(lsp::ExecuteCommandOptions {
23647                    commands: vec!["_the/command".to_string()],
23648                    ..lsp::ExecuteCommandOptions::default()
23649                }),
23650                ..lsp::ServerCapabilities::default()
23651            },
23652            ..FakeLspAdapter::default()
23653        },
23654    );
23655
23656    let editor = workspace
23657        .update(cx, |workspace, window, cx| {
23658            workspace.open_abs_path(
23659                PathBuf::from(path!("/dir/a.ts")),
23660                OpenOptions::default(),
23661                window,
23662                cx,
23663            )
23664        })
23665        .unwrap()
23666        .await
23667        .unwrap()
23668        .downcast::<Editor>()
23669        .unwrap();
23670    cx.executor().run_until_parked();
23671
23672    let fake_server = fake_language_servers.next().await.unwrap();
23673
23674    let buffer = editor.update(cx, |editor, cx| {
23675        editor
23676            .buffer()
23677            .read(cx)
23678            .as_singleton()
23679            .expect("have opened a single file by path")
23680    });
23681
23682    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
23683    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
23684    drop(buffer_snapshot);
23685    let actions = cx
23686        .update_window(*workspace, |_, window, cx| {
23687            project.code_actions(&buffer, anchor..anchor, window, cx)
23688        })
23689        .unwrap();
23690
23691    fake_server
23692        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23693            Ok(Some(vec![
23694                lsp::CodeLens {
23695                    range: lsp::Range::default(),
23696                    command: Some(lsp::Command {
23697                        title: "Code lens command".to_owned(),
23698                        command: "_the/command".to_owned(),
23699                        arguments: None,
23700                    }),
23701                    data: None,
23702                },
23703                lsp::CodeLens {
23704                    range: lsp::Range::default(),
23705                    command: Some(lsp::Command {
23706                        title: "Command not in capabilities".to_owned(),
23707                        command: "not in capabilities".to_owned(),
23708                        arguments: None,
23709                    }),
23710                    data: None,
23711                },
23712                lsp::CodeLens {
23713                    range: lsp::Range {
23714                        start: lsp::Position {
23715                            line: 1,
23716                            character: 1,
23717                        },
23718                        end: lsp::Position {
23719                            line: 1,
23720                            character: 1,
23721                        },
23722                    },
23723                    command: Some(lsp::Command {
23724                        title: "Command not in range".to_owned(),
23725                        command: "_the/command".to_owned(),
23726                        arguments: None,
23727                    }),
23728                    data: None,
23729                },
23730            ]))
23731        })
23732        .next()
23733        .await;
23734
23735    let actions = actions.await.unwrap();
23736    assert_eq!(
23737        actions.len(),
23738        1,
23739        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
23740    );
23741    let action = actions[0].clone();
23742    let apply = project.update(cx, |project, cx| {
23743        project.apply_code_action(buffer.clone(), action, true, cx)
23744    });
23745
23746    // Resolving the code action does not populate its edits. In absence of
23747    // edits, we must execute the given command.
23748    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
23749        |mut lens, _| async move {
23750            let lens_command = lens.command.as_mut().expect("should have a command");
23751            assert_eq!(lens_command.title, "Code lens command");
23752            lens_command.arguments = Some(vec![json!("the-argument")]);
23753            Ok(lens)
23754        },
23755    );
23756
23757    // While executing the command, the language server sends the editor
23758    // a `workspaceEdit` request.
23759    fake_server
23760        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
23761            let fake = fake_server.clone();
23762            move |params, _| {
23763                assert_eq!(params.command, "_the/command");
23764                let fake = fake.clone();
23765                async move {
23766                    fake.server
23767                        .request::<lsp::request::ApplyWorkspaceEdit>(
23768                            lsp::ApplyWorkspaceEditParams {
23769                                label: None,
23770                                edit: lsp::WorkspaceEdit {
23771                                    changes: Some(
23772                                        [(
23773                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
23774                                            vec![lsp::TextEdit {
23775                                                range: lsp::Range::new(
23776                                                    lsp::Position::new(0, 0),
23777                                                    lsp::Position::new(0, 0),
23778                                                ),
23779                                                new_text: "X".into(),
23780                                            }],
23781                                        )]
23782                                        .into_iter()
23783                                        .collect(),
23784                                    ),
23785                                    ..lsp::WorkspaceEdit::default()
23786                                },
23787                            },
23788                        )
23789                        .await
23790                        .into_response()
23791                        .unwrap();
23792                    Ok(Some(json!(null)))
23793                }
23794            }
23795        })
23796        .next()
23797        .await;
23798
23799    // Applying the code lens command returns a project transaction containing the edits
23800    // sent by the language server in its `workspaceEdit` request.
23801    let transaction = apply.await.unwrap();
23802    assert!(transaction.0.contains_key(&buffer));
23803    buffer.update(cx, |buffer, cx| {
23804        assert_eq!(buffer.text(), "Xa");
23805        buffer.undo(cx);
23806        assert_eq!(buffer.text(), "a");
23807    });
23808
23809    let actions_after_edits = cx
23810        .update_window(*workspace, |_, window, cx| {
23811            project.code_actions(&buffer, anchor..anchor, window, cx)
23812        })
23813        .unwrap()
23814        .await
23815        .unwrap();
23816    assert_eq!(
23817        actions, actions_after_edits,
23818        "For the same selection, same code lens actions should be returned"
23819    );
23820
23821    let _responses =
23822        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23823            panic!("No more code lens requests are expected");
23824        });
23825    editor.update_in(cx, |editor, window, cx| {
23826        editor.select_all(&SelectAll, window, cx);
23827    });
23828    cx.executor().run_until_parked();
23829    let new_actions = cx
23830        .update_window(*workspace, |_, window, cx| {
23831            project.code_actions(&buffer, anchor..anchor, window, cx)
23832        })
23833        .unwrap()
23834        .await
23835        .unwrap();
23836    assert_eq!(
23837        actions, new_actions,
23838        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
23839    );
23840}
23841
23842#[gpui::test]
23843async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
23844    init_test(cx, |_| {});
23845
23846    let fs = FakeFs::new(cx.executor());
23847    let main_text = r#"fn main() {
23848println!("1");
23849println!("2");
23850println!("3");
23851println!("4");
23852println!("5");
23853}"#;
23854    let lib_text = "mod foo {}";
23855    fs.insert_tree(
23856        path!("/a"),
23857        json!({
23858            "lib.rs": lib_text,
23859            "main.rs": main_text,
23860        }),
23861    )
23862    .await;
23863
23864    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23865    let (workspace, cx) =
23866        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23867    let worktree_id = workspace.update(cx, |workspace, cx| {
23868        workspace.project().update(cx, |project, cx| {
23869            project.worktrees(cx).next().unwrap().read(cx).id()
23870        })
23871    });
23872
23873    let expected_ranges = vec![
23874        Point::new(0, 0)..Point::new(0, 0),
23875        Point::new(1, 0)..Point::new(1, 1),
23876        Point::new(2, 0)..Point::new(2, 2),
23877        Point::new(3, 0)..Point::new(3, 3),
23878    ];
23879
23880    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23881    let editor_1 = workspace
23882        .update_in(cx, |workspace, window, cx| {
23883            workspace.open_path(
23884                (worktree_id, rel_path("main.rs")),
23885                Some(pane_1.downgrade()),
23886                true,
23887                window,
23888                cx,
23889            )
23890        })
23891        .unwrap()
23892        .await
23893        .downcast::<Editor>()
23894        .unwrap();
23895    pane_1.update(cx, |pane, cx| {
23896        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23897        open_editor.update(cx, |editor, cx| {
23898            assert_eq!(
23899                editor.display_text(cx),
23900                main_text,
23901                "Original main.rs text on initial open",
23902            );
23903            assert_eq!(
23904                editor
23905                    .selections
23906                    .all::<Point>(&editor.display_snapshot(cx))
23907                    .into_iter()
23908                    .map(|s| s.range())
23909                    .collect::<Vec<_>>(),
23910                vec![Point::zero()..Point::zero()],
23911                "Default selections on initial open",
23912            );
23913        })
23914    });
23915    editor_1.update_in(cx, |editor, window, cx| {
23916        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23917            s.select_ranges(expected_ranges.clone());
23918        });
23919    });
23920
23921    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
23922        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
23923    });
23924    let editor_2 = workspace
23925        .update_in(cx, |workspace, window, cx| {
23926            workspace.open_path(
23927                (worktree_id, rel_path("main.rs")),
23928                Some(pane_2.downgrade()),
23929                true,
23930                window,
23931                cx,
23932            )
23933        })
23934        .unwrap()
23935        .await
23936        .downcast::<Editor>()
23937        .unwrap();
23938    pane_2.update(cx, |pane, cx| {
23939        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23940        open_editor.update(cx, |editor, cx| {
23941            assert_eq!(
23942                editor.display_text(cx),
23943                main_text,
23944                "Original main.rs text on initial open in another panel",
23945            );
23946            assert_eq!(
23947                editor
23948                    .selections
23949                    .all::<Point>(&editor.display_snapshot(cx))
23950                    .into_iter()
23951                    .map(|s| s.range())
23952                    .collect::<Vec<_>>(),
23953                vec![Point::zero()..Point::zero()],
23954                "Default selections on initial open in another panel",
23955            );
23956        })
23957    });
23958
23959    editor_2.update_in(cx, |editor, window, cx| {
23960        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
23961    });
23962
23963    let _other_editor_1 = workspace
23964        .update_in(cx, |workspace, window, cx| {
23965            workspace.open_path(
23966                (worktree_id, rel_path("lib.rs")),
23967                Some(pane_1.downgrade()),
23968                true,
23969                window,
23970                cx,
23971            )
23972        })
23973        .unwrap()
23974        .await
23975        .downcast::<Editor>()
23976        .unwrap();
23977    pane_1
23978        .update_in(cx, |pane, window, cx| {
23979            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23980        })
23981        .await
23982        .unwrap();
23983    drop(editor_1);
23984    pane_1.update(cx, |pane, cx| {
23985        pane.active_item()
23986            .unwrap()
23987            .downcast::<Editor>()
23988            .unwrap()
23989            .update(cx, |editor, cx| {
23990                assert_eq!(
23991                    editor.display_text(cx),
23992                    lib_text,
23993                    "Other file should be open and active",
23994                );
23995            });
23996        assert_eq!(pane.items().count(), 1, "No other editors should be open");
23997    });
23998
23999    let _other_editor_2 = workspace
24000        .update_in(cx, |workspace, window, cx| {
24001            workspace.open_path(
24002                (worktree_id, rel_path("lib.rs")),
24003                Some(pane_2.downgrade()),
24004                true,
24005                window,
24006                cx,
24007            )
24008        })
24009        .unwrap()
24010        .await
24011        .downcast::<Editor>()
24012        .unwrap();
24013    pane_2
24014        .update_in(cx, |pane, window, cx| {
24015            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
24016        })
24017        .await
24018        .unwrap();
24019    drop(editor_2);
24020    pane_2.update(cx, |pane, cx| {
24021        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24022        open_editor.update(cx, |editor, cx| {
24023            assert_eq!(
24024                editor.display_text(cx),
24025                lib_text,
24026                "Other file should be open and active in another panel too",
24027            );
24028        });
24029        assert_eq!(
24030            pane.items().count(),
24031            1,
24032            "No other editors should be open in another pane",
24033        );
24034    });
24035
24036    let _editor_1_reopened = workspace
24037        .update_in(cx, |workspace, window, cx| {
24038            workspace.open_path(
24039                (worktree_id, rel_path("main.rs")),
24040                Some(pane_1.downgrade()),
24041                true,
24042                window,
24043                cx,
24044            )
24045        })
24046        .unwrap()
24047        .await
24048        .downcast::<Editor>()
24049        .unwrap();
24050    let _editor_2_reopened = workspace
24051        .update_in(cx, |workspace, window, cx| {
24052            workspace.open_path(
24053                (worktree_id, rel_path("main.rs")),
24054                Some(pane_2.downgrade()),
24055                true,
24056                window,
24057                cx,
24058            )
24059        })
24060        .unwrap()
24061        .await
24062        .downcast::<Editor>()
24063        .unwrap();
24064    pane_1.update(cx, |pane, cx| {
24065        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24066        open_editor.update(cx, |editor, cx| {
24067            assert_eq!(
24068                editor.display_text(cx),
24069                main_text,
24070                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
24071            );
24072            assert_eq!(
24073                editor
24074                    .selections
24075                    .all::<Point>(&editor.display_snapshot(cx))
24076                    .into_iter()
24077                    .map(|s| s.range())
24078                    .collect::<Vec<_>>(),
24079                expected_ranges,
24080                "Previous editor in the 1st panel had selections and should get them restored on reopen",
24081            );
24082        })
24083    });
24084    pane_2.update(cx, |pane, cx| {
24085        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24086        open_editor.update(cx, |editor, cx| {
24087            assert_eq!(
24088                editor.display_text(cx),
24089                r#"fn main() {
24090⋯rintln!("1");
24091⋯intln!("2");
24092⋯ntln!("3");
24093println!("4");
24094println!("5");
24095}"#,
24096                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
24097            );
24098            assert_eq!(
24099                editor
24100                    .selections
24101                    .all::<Point>(&editor.display_snapshot(cx))
24102                    .into_iter()
24103                    .map(|s| s.range())
24104                    .collect::<Vec<_>>(),
24105                vec![Point::zero()..Point::zero()],
24106                "Previous editor in the 2nd pane had no selections changed hence should restore none",
24107            );
24108        })
24109    });
24110}
24111
24112#[gpui::test]
24113async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
24114    init_test(cx, |_| {});
24115
24116    let fs = FakeFs::new(cx.executor());
24117    let main_text = r#"fn main() {
24118println!("1");
24119println!("2");
24120println!("3");
24121println!("4");
24122println!("5");
24123}"#;
24124    let lib_text = "mod foo {}";
24125    fs.insert_tree(
24126        path!("/a"),
24127        json!({
24128            "lib.rs": lib_text,
24129            "main.rs": main_text,
24130        }),
24131    )
24132    .await;
24133
24134    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24135    let (workspace, cx) =
24136        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24137    let worktree_id = workspace.update(cx, |workspace, cx| {
24138        workspace.project().update(cx, |project, cx| {
24139            project.worktrees(cx).next().unwrap().read(cx).id()
24140        })
24141    });
24142
24143    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24144    let editor = workspace
24145        .update_in(cx, |workspace, window, cx| {
24146            workspace.open_path(
24147                (worktree_id, rel_path("main.rs")),
24148                Some(pane.downgrade()),
24149                true,
24150                window,
24151                cx,
24152            )
24153        })
24154        .unwrap()
24155        .await
24156        .downcast::<Editor>()
24157        .unwrap();
24158    pane.update(cx, |pane, cx| {
24159        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24160        open_editor.update(cx, |editor, cx| {
24161            assert_eq!(
24162                editor.display_text(cx),
24163                main_text,
24164                "Original main.rs text on initial open",
24165            );
24166        })
24167    });
24168    editor.update_in(cx, |editor, window, cx| {
24169        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
24170    });
24171
24172    cx.update_global(|store: &mut SettingsStore, cx| {
24173        store.update_user_settings(cx, |s| {
24174            s.workspace.restore_on_file_reopen = Some(false);
24175        });
24176    });
24177    editor.update_in(cx, |editor, window, cx| {
24178        editor.fold_ranges(
24179            vec![
24180                Point::new(1, 0)..Point::new(1, 1),
24181                Point::new(2, 0)..Point::new(2, 2),
24182                Point::new(3, 0)..Point::new(3, 3),
24183            ],
24184            false,
24185            window,
24186            cx,
24187        );
24188    });
24189    pane.update_in(cx, |pane, window, cx| {
24190        pane.close_all_items(&CloseAllItems::default(), window, cx)
24191    })
24192    .await
24193    .unwrap();
24194    pane.update(cx, |pane, _| {
24195        assert!(pane.active_item().is_none());
24196    });
24197    cx.update_global(|store: &mut SettingsStore, cx| {
24198        store.update_user_settings(cx, |s| {
24199            s.workspace.restore_on_file_reopen = Some(true);
24200        });
24201    });
24202
24203    let _editor_reopened = workspace
24204        .update_in(cx, |workspace, window, cx| {
24205            workspace.open_path(
24206                (worktree_id, rel_path("main.rs")),
24207                Some(pane.downgrade()),
24208                true,
24209                window,
24210                cx,
24211            )
24212        })
24213        .unwrap()
24214        .await
24215        .downcast::<Editor>()
24216        .unwrap();
24217    pane.update(cx, |pane, cx| {
24218        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24219        open_editor.update(cx, |editor, cx| {
24220            assert_eq!(
24221                editor.display_text(cx),
24222                main_text,
24223                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
24224            );
24225        })
24226    });
24227}
24228
24229#[gpui::test]
24230async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
24231    struct EmptyModalView {
24232        focus_handle: gpui::FocusHandle,
24233    }
24234    impl EventEmitter<DismissEvent> for EmptyModalView {}
24235    impl Render for EmptyModalView {
24236        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
24237            div()
24238        }
24239    }
24240    impl Focusable for EmptyModalView {
24241        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
24242            self.focus_handle.clone()
24243        }
24244    }
24245    impl workspace::ModalView for EmptyModalView {}
24246    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
24247        EmptyModalView {
24248            focus_handle: cx.focus_handle(),
24249        }
24250    }
24251
24252    init_test(cx, |_| {});
24253
24254    let fs = FakeFs::new(cx.executor());
24255    let project = Project::test(fs, [], cx).await;
24256    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24257    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
24258    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24259    let editor = cx.new_window_entity(|window, cx| {
24260        Editor::new(
24261            EditorMode::full(),
24262            buffer,
24263            Some(project.clone()),
24264            window,
24265            cx,
24266        )
24267    });
24268    workspace
24269        .update(cx, |workspace, window, cx| {
24270            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
24271        })
24272        .unwrap();
24273    editor.update_in(cx, |editor, window, cx| {
24274        editor.open_context_menu(&OpenContextMenu, window, cx);
24275        assert!(editor.mouse_context_menu.is_some());
24276    });
24277    workspace
24278        .update(cx, |workspace, window, cx| {
24279            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
24280        })
24281        .unwrap();
24282    cx.read(|cx| {
24283        assert!(editor.read(cx).mouse_context_menu.is_none());
24284    });
24285}
24286
24287fn set_linked_edit_ranges(
24288    opening: (Point, Point),
24289    closing: (Point, Point),
24290    editor: &mut Editor,
24291    cx: &mut Context<Editor>,
24292) {
24293    let Some((buffer, _)) = editor
24294        .buffer
24295        .read(cx)
24296        .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
24297    else {
24298        panic!("Failed to get buffer for selection position");
24299    };
24300    let buffer = buffer.read(cx);
24301    let buffer_id = buffer.remote_id();
24302    let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
24303    let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
24304    let mut linked_ranges = HashMap::default();
24305    linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
24306    editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
24307}
24308
24309#[gpui::test]
24310async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
24311    init_test(cx, |_| {});
24312
24313    let fs = FakeFs::new(cx.executor());
24314    fs.insert_file(path!("/file.html"), Default::default())
24315        .await;
24316
24317    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
24318
24319    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24320    let html_language = Arc::new(Language::new(
24321        LanguageConfig {
24322            name: "HTML".into(),
24323            matcher: LanguageMatcher {
24324                path_suffixes: vec!["html".to_string()],
24325                ..LanguageMatcher::default()
24326            },
24327            brackets: BracketPairConfig {
24328                pairs: vec![BracketPair {
24329                    start: "<".into(),
24330                    end: ">".into(),
24331                    close: true,
24332                    ..Default::default()
24333                }],
24334                ..Default::default()
24335            },
24336            ..Default::default()
24337        },
24338        Some(tree_sitter_html::LANGUAGE.into()),
24339    ));
24340    language_registry.add(html_language);
24341    let mut fake_servers = language_registry.register_fake_lsp(
24342        "HTML",
24343        FakeLspAdapter {
24344            capabilities: lsp::ServerCapabilities {
24345                completion_provider: Some(lsp::CompletionOptions {
24346                    resolve_provider: Some(true),
24347                    ..Default::default()
24348                }),
24349                ..Default::default()
24350            },
24351            ..Default::default()
24352        },
24353    );
24354
24355    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24356    let cx = &mut VisualTestContext::from_window(*workspace, cx);
24357
24358    let worktree_id = workspace
24359        .update(cx, |workspace, _window, cx| {
24360            workspace.project().update(cx, |project, cx| {
24361                project.worktrees(cx).next().unwrap().read(cx).id()
24362            })
24363        })
24364        .unwrap();
24365    project
24366        .update(cx, |project, cx| {
24367            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
24368        })
24369        .await
24370        .unwrap();
24371    let editor = workspace
24372        .update(cx, |workspace, window, cx| {
24373            workspace.open_path((worktree_id, rel_path("file.html")), None, true, window, cx)
24374        })
24375        .unwrap()
24376        .await
24377        .unwrap()
24378        .downcast::<Editor>()
24379        .unwrap();
24380
24381    let fake_server = fake_servers.next().await.unwrap();
24382    editor.update_in(cx, |editor, window, cx| {
24383        editor.set_text("<ad></ad>", window, cx);
24384        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24385            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
24386        });
24387        set_linked_edit_ranges(
24388            (Point::new(0, 1), Point::new(0, 3)),
24389            (Point::new(0, 6), Point::new(0, 8)),
24390            editor,
24391            cx,
24392        );
24393    });
24394    let mut completion_handle =
24395        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
24396            Ok(Some(lsp::CompletionResponse::Array(vec![
24397                lsp::CompletionItem {
24398                    label: "head".to_string(),
24399                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
24400                        lsp::InsertReplaceEdit {
24401                            new_text: "head".to_string(),
24402                            insert: lsp::Range::new(
24403                                lsp::Position::new(0, 1),
24404                                lsp::Position::new(0, 3),
24405                            ),
24406                            replace: lsp::Range::new(
24407                                lsp::Position::new(0, 1),
24408                                lsp::Position::new(0, 3),
24409                            ),
24410                        },
24411                    )),
24412                    ..Default::default()
24413                },
24414            ])))
24415        });
24416    editor.update_in(cx, |editor, window, cx| {
24417        editor.show_completions(&ShowCompletions, window, cx);
24418    });
24419    cx.run_until_parked();
24420    completion_handle.next().await.unwrap();
24421    editor.update(cx, |editor, _| {
24422        assert!(
24423            editor.context_menu_visible(),
24424            "Completion menu should be visible"
24425        );
24426    });
24427    editor.update_in(cx, |editor, window, cx| {
24428        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
24429    });
24430    cx.executor().run_until_parked();
24431    editor.update(cx, |editor, cx| {
24432        assert_eq!(editor.text(cx), "<head></head>");
24433    });
24434}
24435
24436#[gpui::test]
24437async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
24438    init_test(cx, |_| {});
24439
24440    let mut cx = EditorTestContext::new(cx).await;
24441    let language = Arc::new(Language::new(
24442        LanguageConfig {
24443            name: "TSX".into(),
24444            matcher: LanguageMatcher {
24445                path_suffixes: vec!["tsx".to_string()],
24446                ..LanguageMatcher::default()
24447            },
24448            brackets: BracketPairConfig {
24449                pairs: vec![BracketPair {
24450                    start: "<".into(),
24451                    end: ">".into(),
24452                    close: true,
24453                    ..Default::default()
24454                }],
24455                ..Default::default()
24456            },
24457            linked_edit_characters: HashSet::from_iter(['.']),
24458            ..Default::default()
24459        },
24460        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
24461    ));
24462    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24463
24464    // Test typing > does not extend linked pair
24465    cx.set_state("<divˇ<div></div>");
24466    cx.update_editor(|editor, _, cx| {
24467        set_linked_edit_ranges(
24468            (Point::new(0, 1), Point::new(0, 4)),
24469            (Point::new(0, 11), Point::new(0, 14)),
24470            editor,
24471            cx,
24472        );
24473    });
24474    cx.update_editor(|editor, window, cx| {
24475        editor.handle_input(">", window, cx);
24476    });
24477    cx.assert_editor_state("<div>ˇ<div></div>");
24478
24479    // Test typing . do extend linked pair
24480    cx.set_state("<Animatedˇ></Animated>");
24481    cx.update_editor(|editor, _, cx| {
24482        set_linked_edit_ranges(
24483            (Point::new(0, 1), Point::new(0, 9)),
24484            (Point::new(0, 12), Point::new(0, 20)),
24485            editor,
24486            cx,
24487        );
24488    });
24489    cx.update_editor(|editor, window, cx| {
24490        editor.handle_input(".", window, cx);
24491    });
24492    cx.assert_editor_state("<Animated.ˇ></Animated.>");
24493    cx.update_editor(|editor, _, cx| {
24494        set_linked_edit_ranges(
24495            (Point::new(0, 1), Point::new(0, 10)),
24496            (Point::new(0, 13), Point::new(0, 21)),
24497            editor,
24498            cx,
24499        );
24500    });
24501    cx.update_editor(|editor, window, cx| {
24502        editor.handle_input("V", window, cx);
24503    });
24504    cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
24505}
24506
24507#[gpui::test]
24508async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
24509    init_test(cx, |_| {});
24510
24511    let fs = FakeFs::new(cx.executor());
24512    fs.insert_tree(
24513        path!("/root"),
24514        json!({
24515            "a": {
24516                "main.rs": "fn main() {}",
24517            },
24518            "foo": {
24519                "bar": {
24520                    "external_file.rs": "pub mod external {}",
24521                }
24522            }
24523        }),
24524    )
24525    .await;
24526
24527    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
24528    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24529    language_registry.add(rust_lang());
24530    let _fake_servers = language_registry.register_fake_lsp(
24531        "Rust",
24532        FakeLspAdapter {
24533            ..FakeLspAdapter::default()
24534        },
24535    );
24536    let (workspace, cx) =
24537        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24538    let worktree_id = workspace.update(cx, |workspace, cx| {
24539        workspace.project().update(cx, |project, cx| {
24540            project.worktrees(cx).next().unwrap().read(cx).id()
24541        })
24542    });
24543
24544    let assert_language_servers_count =
24545        |expected: usize, context: &str, cx: &mut VisualTestContext| {
24546            project.update(cx, |project, cx| {
24547                let current = project
24548                    .lsp_store()
24549                    .read(cx)
24550                    .as_local()
24551                    .unwrap()
24552                    .language_servers
24553                    .len();
24554                assert_eq!(expected, current, "{context}");
24555            });
24556        };
24557
24558    assert_language_servers_count(
24559        0,
24560        "No servers should be running before any file is open",
24561        cx,
24562    );
24563    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24564    let main_editor = workspace
24565        .update_in(cx, |workspace, window, cx| {
24566            workspace.open_path(
24567                (worktree_id, rel_path("main.rs")),
24568                Some(pane.downgrade()),
24569                true,
24570                window,
24571                cx,
24572            )
24573        })
24574        .unwrap()
24575        .await
24576        .downcast::<Editor>()
24577        .unwrap();
24578    pane.update(cx, |pane, cx| {
24579        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24580        open_editor.update(cx, |editor, cx| {
24581            assert_eq!(
24582                editor.display_text(cx),
24583                "fn main() {}",
24584                "Original main.rs text on initial open",
24585            );
24586        });
24587        assert_eq!(open_editor, main_editor);
24588    });
24589    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
24590
24591    let external_editor = workspace
24592        .update_in(cx, |workspace, window, cx| {
24593            workspace.open_abs_path(
24594                PathBuf::from("/root/foo/bar/external_file.rs"),
24595                OpenOptions::default(),
24596                window,
24597                cx,
24598            )
24599        })
24600        .await
24601        .expect("opening external file")
24602        .downcast::<Editor>()
24603        .expect("downcasted external file's open element to editor");
24604    pane.update(cx, |pane, cx| {
24605        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24606        open_editor.update(cx, |editor, cx| {
24607            assert_eq!(
24608                editor.display_text(cx),
24609                "pub mod external {}",
24610                "External file is open now",
24611            );
24612        });
24613        assert_eq!(open_editor, external_editor);
24614    });
24615    assert_language_servers_count(
24616        1,
24617        "Second, external, *.rs file should join the existing server",
24618        cx,
24619    );
24620
24621    pane.update_in(cx, |pane, window, cx| {
24622        pane.close_active_item(&CloseActiveItem::default(), window, cx)
24623    })
24624    .await
24625    .unwrap();
24626    pane.update_in(cx, |pane, window, cx| {
24627        pane.navigate_backward(&Default::default(), window, cx);
24628    });
24629    cx.run_until_parked();
24630    pane.update(cx, |pane, cx| {
24631        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24632        open_editor.update(cx, |editor, cx| {
24633            assert_eq!(
24634                editor.display_text(cx),
24635                "pub mod external {}",
24636                "External file is open now",
24637            );
24638        });
24639    });
24640    assert_language_servers_count(
24641        1,
24642        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
24643        cx,
24644    );
24645
24646    cx.update(|_, cx| {
24647        workspace::reload(cx);
24648    });
24649    assert_language_servers_count(
24650        1,
24651        "After reloading the worktree with local and external files opened, only one project should be started",
24652        cx,
24653    );
24654}
24655
24656#[gpui::test]
24657async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
24658    init_test(cx, |_| {});
24659
24660    let mut cx = EditorTestContext::new(cx).await;
24661    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24662    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24663
24664    // test cursor move to start of each line on tab
24665    // for `if`, `elif`, `else`, `while`, `with` and `for`
24666    cx.set_state(indoc! {"
24667        def main():
24668        ˇ    for item in items:
24669        ˇ        while item.active:
24670        ˇ            if item.value > 10:
24671        ˇ                continue
24672        ˇ            elif item.value < 0:
24673        ˇ                break
24674        ˇ            else:
24675        ˇ                with item.context() as ctx:
24676        ˇ                    yield count
24677        ˇ        else:
24678        ˇ            log('while else')
24679        ˇ    else:
24680        ˇ        log('for else')
24681    "});
24682    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24683    cx.assert_editor_state(indoc! {"
24684        def main():
24685            ˇfor item in items:
24686                ˇwhile item.active:
24687                    ˇif item.value > 10:
24688                        ˇcontinue
24689                    ˇelif item.value < 0:
24690                        ˇbreak
24691                    ˇelse:
24692                        ˇwith item.context() as ctx:
24693                            ˇyield count
24694                ˇelse:
24695                    ˇlog('while else')
24696            ˇelse:
24697                ˇlog('for else')
24698    "});
24699    // test relative indent is preserved when tab
24700    // for `if`, `elif`, `else`, `while`, `with` and `for`
24701    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24702    cx.assert_editor_state(indoc! {"
24703        def main():
24704                ˇfor item in items:
24705                    ˇwhile item.active:
24706                        ˇif item.value > 10:
24707                            ˇcontinue
24708                        ˇelif item.value < 0:
24709                            ˇbreak
24710                        ˇelse:
24711                            ˇwith item.context() as ctx:
24712                                ˇyield count
24713                    ˇelse:
24714                        ˇlog('while else')
24715                ˇelse:
24716                    ˇlog('for else')
24717    "});
24718
24719    // test cursor move to start of each line on tab
24720    // for `try`, `except`, `else`, `finally`, `match` and `def`
24721    cx.set_state(indoc! {"
24722        def main():
24723        ˇ    try:
24724        ˇ        fetch()
24725        ˇ    except ValueError:
24726        ˇ        handle_error()
24727        ˇ    else:
24728        ˇ        match value:
24729        ˇ            case _:
24730        ˇ    finally:
24731        ˇ        def status():
24732        ˇ            return 0
24733    "});
24734    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24735    cx.assert_editor_state(indoc! {"
24736        def main():
24737            ˇtry:
24738                ˇfetch()
24739            ˇexcept ValueError:
24740                ˇhandle_error()
24741            ˇelse:
24742                ˇmatch value:
24743                    ˇcase _:
24744            ˇfinally:
24745                ˇdef status():
24746                    ˇreturn 0
24747    "});
24748    // test relative indent is preserved when tab
24749    // for `try`, `except`, `else`, `finally`, `match` and `def`
24750    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24751    cx.assert_editor_state(indoc! {"
24752        def main():
24753                ˇtry:
24754                    ˇfetch()
24755                ˇexcept ValueError:
24756                    ˇhandle_error()
24757                ˇelse:
24758                    ˇmatch value:
24759                        ˇcase _:
24760                ˇfinally:
24761                    ˇdef status():
24762                        ˇreturn 0
24763    "});
24764}
24765
24766#[gpui::test]
24767async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
24768    init_test(cx, |_| {});
24769
24770    let mut cx = EditorTestContext::new(cx).await;
24771    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24772    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24773
24774    // test `else` auto outdents when typed inside `if` block
24775    cx.set_state(indoc! {"
24776        def main():
24777            if i == 2:
24778                return
24779                ˇ
24780    "});
24781    cx.update_editor(|editor, window, cx| {
24782        editor.handle_input("else:", window, cx);
24783    });
24784    cx.assert_editor_state(indoc! {"
24785        def main():
24786            if i == 2:
24787                return
24788            else:ˇ
24789    "});
24790
24791    // test `except` auto outdents when typed inside `try` block
24792    cx.set_state(indoc! {"
24793        def main():
24794            try:
24795                i = 2
24796                ˇ
24797    "});
24798    cx.update_editor(|editor, window, cx| {
24799        editor.handle_input("except:", window, cx);
24800    });
24801    cx.assert_editor_state(indoc! {"
24802        def main():
24803            try:
24804                i = 2
24805            except:ˇ
24806    "});
24807
24808    // test `else` auto outdents when typed inside `except` block
24809    cx.set_state(indoc! {"
24810        def main():
24811            try:
24812                i = 2
24813            except:
24814                j = 2
24815                ˇ
24816    "});
24817    cx.update_editor(|editor, window, cx| {
24818        editor.handle_input("else:", 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    "});
24828
24829    // test `finally` auto outdents when typed inside `else` block
24830    cx.set_state(indoc! {"
24831        def main():
24832            try:
24833                i = 2
24834            except:
24835                j = 2
24836            else:
24837                k = 2
24838                ˇ
24839    "});
24840    cx.update_editor(|editor, window, cx| {
24841        editor.handle_input("finally:", window, cx);
24842    });
24843    cx.assert_editor_state(indoc! {"
24844        def main():
24845            try:
24846                i = 2
24847            except:
24848                j = 2
24849            else:
24850                k = 2
24851            finally:ˇ
24852    "});
24853
24854    // test `else` does not outdents when typed inside `except` block right after for block
24855    cx.set_state(indoc! {"
24856        def main():
24857            try:
24858                i = 2
24859            except:
24860                for i in range(n):
24861                    pass
24862                ˇ
24863    "});
24864    cx.update_editor(|editor, window, cx| {
24865        editor.handle_input("else:", window, cx);
24866    });
24867    cx.assert_editor_state(indoc! {"
24868        def main():
24869            try:
24870                i = 2
24871            except:
24872                for i in range(n):
24873                    pass
24874                else:ˇ
24875    "});
24876
24877    // test `finally` auto outdents when typed inside `else` block right after for block
24878    cx.set_state(indoc! {"
24879        def main():
24880            try:
24881                i = 2
24882            except:
24883                j = 2
24884            else:
24885                for i in range(n):
24886                    pass
24887                ˇ
24888    "});
24889    cx.update_editor(|editor, window, cx| {
24890        editor.handle_input("finally:", window, cx);
24891    });
24892    cx.assert_editor_state(indoc! {"
24893        def main():
24894            try:
24895                i = 2
24896            except:
24897                j = 2
24898            else:
24899                for i in range(n):
24900                    pass
24901            finally:ˇ
24902    "});
24903
24904    // test `except` outdents to inner "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 `except` outdents to outer "try" block
24928    cx.set_state(indoc! {"
24929        def main():
24930            try:
24931                i = 2
24932                if i == 2:
24933                    try:
24934                        i = 3
24935                ˇ
24936    "});
24937    cx.update_editor(|editor, window, cx| {
24938        editor.handle_input("except:", window, cx);
24939    });
24940    cx.assert_editor_state(indoc! {"
24941        def main():
24942            try:
24943                i = 2
24944                if i == 2:
24945                    try:
24946                        i = 3
24947            except:ˇ
24948    "});
24949
24950    // test `else` stays at correct indent when typed after `for` block
24951    cx.set_state(indoc! {"
24952        def main():
24953            for i in range(10):
24954                if i == 3:
24955                    break
24956            ˇ
24957    "});
24958    cx.update_editor(|editor, window, cx| {
24959        editor.handle_input("else:", window, cx);
24960    });
24961    cx.assert_editor_state(indoc! {"
24962        def main():
24963            for i in range(10):
24964                if i == 3:
24965                    break
24966            else:ˇ
24967    "});
24968
24969    // test does not outdent on typing after line with square brackets
24970    cx.set_state(indoc! {"
24971        def f() -> list[str]:
24972            ˇ
24973    "});
24974    cx.update_editor(|editor, window, cx| {
24975        editor.handle_input("a", window, cx);
24976    });
24977    cx.assert_editor_state(indoc! {"
24978        def f() -> list[str]:
2497924980    "});
24981
24982    // test does not outdent on typing : after case keyword
24983    cx.set_state(indoc! {"
24984        match 1:
24985            caseˇ
24986    "});
24987    cx.update_editor(|editor, window, cx| {
24988        editor.handle_input(":", window, cx);
24989    });
24990    cx.assert_editor_state(indoc! {"
24991        match 1:
24992            case:ˇ
24993    "});
24994}
24995
24996#[gpui::test]
24997async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
24998    init_test(cx, |_| {});
24999    update_test_language_settings(cx, |settings| {
25000        settings.defaults.extend_comment_on_newline = Some(false);
25001    });
25002    let mut cx = EditorTestContext::new(cx).await;
25003    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
25004    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25005
25006    // test correct indent after newline on comment
25007    cx.set_state(indoc! {"
25008        # COMMENT:ˇ
25009    "});
25010    cx.update_editor(|editor, window, cx| {
25011        editor.newline(&Newline, window, cx);
25012    });
25013    cx.assert_editor_state(indoc! {"
25014        # COMMENT:
25015        ˇ
25016    "});
25017
25018    // test correct indent after newline in brackets
25019    cx.set_state(indoc! {"
25020        {ˇ}
25021    "});
25022    cx.update_editor(|editor, window, cx| {
25023        editor.newline(&Newline, window, cx);
25024    });
25025    cx.run_until_parked();
25026    cx.assert_editor_state(indoc! {"
25027        {
25028            ˇ
25029        }
25030    "});
25031
25032    cx.set_state(indoc! {"
25033        (ˇ)
25034    "});
25035    cx.update_editor(|editor, window, cx| {
25036        editor.newline(&Newline, window, cx);
25037    });
25038    cx.run_until_parked();
25039    cx.assert_editor_state(indoc! {"
25040        (
25041            ˇ
25042        )
25043    "});
25044
25045    // do not indent after empty lists or dictionaries
25046    cx.set_state(indoc! {"
25047        a = []ˇ
25048    "});
25049    cx.update_editor(|editor, window, cx| {
25050        editor.newline(&Newline, window, cx);
25051    });
25052    cx.run_until_parked();
25053    cx.assert_editor_state(indoc! {"
25054        a = []
25055        ˇ
25056    "});
25057}
25058
25059#[gpui::test]
25060async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
25061    init_test(cx, |_| {});
25062
25063    let mut cx = EditorTestContext::new(cx).await;
25064    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25065    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25066
25067    // test cursor move to start of each line on tab
25068    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
25069    cx.set_state(indoc! {"
25070        function main() {
25071        ˇ    for item in $items; do
25072        ˇ        while [ -n \"$item\" ]; do
25073        ˇ            if [ \"$value\" -gt 10 ]; then
25074        ˇ                continue
25075        ˇ            elif [ \"$value\" -lt 0 ]; then
25076        ˇ                break
25077        ˇ            else
25078        ˇ                echo \"$item\"
25079        ˇ            fi
25080        ˇ        done
25081        ˇ    done
25082        ˇ}
25083    "});
25084    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25085    cx.assert_editor_state(indoc! {"
25086        function main() {
25087            ˇfor item in $items; do
25088                ˇwhile [ -n \"$item\" ]; do
25089                    ˇif [ \"$value\" -gt 10 ]; then
25090                        ˇcontinue
25091                    ˇelif [ \"$value\" -lt 0 ]; then
25092                        ˇbreak
25093                    ˇelse
25094                        ˇecho \"$item\"
25095                    ˇfi
25096                ˇdone
25097            ˇdone
25098        ˇ}
25099    "});
25100    // test relative indent is preserved when tab
25101    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25102    cx.assert_editor_state(indoc! {"
25103        function main() {
25104                ˇfor item in $items; do
25105                    ˇwhile [ -n \"$item\" ]; do
25106                        ˇif [ \"$value\" -gt 10 ]; then
25107                            ˇcontinue
25108                        ˇelif [ \"$value\" -lt 0 ]; then
25109                            ˇbreak
25110                        ˇelse
25111                            ˇecho \"$item\"
25112                        ˇfi
25113                    ˇdone
25114                ˇdone
25115            ˇ}
25116    "});
25117
25118    // test cursor move to start of each line on tab
25119    // for `case` statement with patterns
25120    cx.set_state(indoc! {"
25121        function handle() {
25122        ˇ    case \"$1\" in
25123        ˇ        start)
25124        ˇ            echo \"a\"
25125        ˇ            ;;
25126        ˇ        stop)
25127        ˇ            echo \"b\"
25128        ˇ            ;;
25129        ˇ        *)
25130        ˇ            echo \"c\"
25131        ˇ            ;;
25132        ˇ    esac
25133        ˇ}
25134    "});
25135    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25136    cx.assert_editor_state(indoc! {"
25137        function handle() {
25138            ˇcase \"$1\" in
25139                ˇstart)
25140                    ˇecho \"a\"
25141                    ˇ;;
25142                ˇstop)
25143                    ˇecho \"b\"
25144                    ˇ;;
25145                ˇ*)
25146                    ˇecho \"c\"
25147                    ˇ;;
25148            ˇesac
25149        ˇ}
25150    "});
25151}
25152
25153#[gpui::test]
25154async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
25155    init_test(cx, |_| {});
25156
25157    let mut cx = EditorTestContext::new(cx).await;
25158    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25159    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25160
25161    // test indents on comment insert
25162    cx.set_state(indoc! {"
25163        function main() {
25164        ˇ    for item in $items; do
25165        ˇ        while [ -n \"$item\" ]; do
25166        ˇ            if [ \"$value\" -gt 10 ]; then
25167        ˇ                continue
25168        ˇ            elif [ \"$value\" -lt 0 ]; then
25169        ˇ                break
25170        ˇ            else
25171        ˇ                echo \"$item\"
25172        ˇ            fi
25173        ˇ        done
25174        ˇ    done
25175        ˇ}
25176    "});
25177    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
25178    cx.assert_editor_state(indoc! {"
25179        function main() {
25180        #ˇ    for item in $items; do
25181        #ˇ        while [ -n \"$item\" ]; do
25182        #ˇ            if [ \"$value\" -gt 10 ]; then
25183        #ˇ                continue
25184        #ˇ            elif [ \"$value\" -lt 0 ]; then
25185        #ˇ                break
25186        #ˇ            else
25187        #ˇ                echo \"$item\"
25188        #ˇ            fi
25189        #ˇ        done
25190        #ˇ    done
25191        #ˇ}
25192    "});
25193}
25194
25195#[gpui::test]
25196async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
25197    init_test(cx, |_| {});
25198
25199    let mut cx = EditorTestContext::new(cx).await;
25200    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25201    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25202
25203    // test `else` auto outdents when typed inside `if` block
25204    cx.set_state(indoc! {"
25205        if [ \"$1\" = \"test\" ]; then
25206            echo \"foo bar\"
25207            ˇ
25208    "});
25209    cx.update_editor(|editor, window, cx| {
25210        editor.handle_input("else", window, cx);
25211    });
25212    cx.assert_editor_state(indoc! {"
25213        if [ \"$1\" = \"test\" ]; then
25214            echo \"foo bar\"
25215        elseˇ
25216    "});
25217
25218    // test `elif` auto outdents when typed inside `if` block
25219    cx.set_state(indoc! {"
25220        if [ \"$1\" = \"test\" ]; then
25221            echo \"foo bar\"
25222            ˇ
25223    "});
25224    cx.update_editor(|editor, window, cx| {
25225        editor.handle_input("elif", window, cx);
25226    });
25227    cx.assert_editor_state(indoc! {"
25228        if [ \"$1\" = \"test\" ]; then
25229            echo \"foo bar\"
25230        elifˇ
25231    "});
25232
25233    // test `fi` auto outdents when typed inside `else` block
25234    cx.set_state(indoc! {"
25235        if [ \"$1\" = \"test\" ]; then
25236            echo \"foo bar\"
25237        else
25238            echo \"bar baz\"
25239            ˇ
25240    "});
25241    cx.update_editor(|editor, window, cx| {
25242        editor.handle_input("fi", window, cx);
25243    });
25244    cx.assert_editor_state(indoc! {"
25245        if [ \"$1\" = \"test\" ]; then
25246            echo \"foo bar\"
25247        else
25248            echo \"bar baz\"
25249        fiˇ
25250    "});
25251
25252    // test `done` auto outdents when typed inside `while` block
25253    cx.set_state(indoc! {"
25254        while read line; do
25255            echo \"$line\"
25256            ˇ
25257    "});
25258    cx.update_editor(|editor, window, cx| {
25259        editor.handle_input("done", window, cx);
25260    });
25261    cx.assert_editor_state(indoc! {"
25262        while read line; do
25263            echo \"$line\"
25264        doneˇ
25265    "});
25266
25267    // test `done` auto outdents when typed inside `for` block
25268    cx.set_state(indoc! {"
25269        for file in *.txt; do
25270            cat \"$file\"
25271            ˇ
25272    "});
25273    cx.update_editor(|editor, window, cx| {
25274        editor.handle_input("done", window, cx);
25275    });
25276    cx.assert_editor_state(indoc! {"
25277        for file in *.txt; do
25278            cat \"$file\"
25279        doneˇ
25280    "});
25281
25282    // test `esac` auto outdents when typed inside `case` block
25283    cx.set_state(indoc! {"
25284        case \"$1\" in
25285            start)
25286                echo \"foo bar\"
25287                ;;
25288            stop)
25289                echo \"bar baz\"
25290                ;;
25291            ˇ
25292    "});
25293    cx.update_editor(|editor, window, cx| {
25294        editor.handle_input("esac", window, cx);
25295    });
25296    cx.assert_editor_state(indoc! {"
25297        case \"$1\" in
25298            start)
25299                echo \"foo bar\"
25300                ;;
25301            stop)
25302                echo \"bar baz\"
25303                ;;
25304        esacˇ
25305    "});
25306
25307    // test `*)` auto outdents when typed inside `case` block
25308    cx.set_state(indoc! {"
25309        case \"$1\" in
25310            start)
25311                echo \"foo bar\"
25312                ;;
25313                ˇ
25314    "});
25315    cx.update_editor(|editor, window, cx| {
25316        editor.handle_input("*)", window, cx);
25317    });
25318    cx.assert_editor_state(indoc! {"
25319        case \"$1\" in
25320            start)
25321                echo \"foo bar\"
25322                ;;
25323            *)ˇ
25324    "});
25325
25326    // test `fi` outdents to correct level with nested if blocks
25327    cx.set_state(indoc! {"
25328        if [ \"$1\" = \"test\" ]; then
25329            echo \"outer if\"
25330            if [ \"$2\" = \"debug\" ]; then
25331                echo \"inner if\"
25332                ˇ
25333    "});
25334    cx.update_editor(|editor, window, cx| {
25335        editor.handle_input("fi", window, cx);
25336    });
25337    cx.assert_editor_state(indoc! {"
25338        if [ \"$1\" = \"test\" ]; then
25339            echo \"outer if\"
25340            if [ \"$2\" = \"debug\" ]; then
25341                echo \"inner if\"
25342            fiˇ
25343    "});
25344}
25345
25346#[gpui::test]
25347async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
25348    init_test(cx, |_| {});
25349    update_test_language_settings(cx, |settings| {
25350        settings.defaults.extend_comment_on_newline = Some(false);
25351    });
25352    let mut cx = EditorTestContext::new(cx).await;
25353    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25354    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25355
25356    // test correct indent after newline on comment
25357    cx.set_state(indoc! {"
25358        # COMMENT:ˇ
25359    "});
25360    cx.update_editor(|editor, window, cx| {
25361        editor.newline(&Newline, window, cx);
25362    });
25363    cx.assert_editor_state(indoc! {"
25364        # COMMENT:
25365        ˇ
25366    "});
25367
25368    // test correct indent after newline after `then`
25369    cx.set_state(indoc! {"
25370
25371        if [ \"$1\" = \"test\" ]; thenˇ
25372    "});
25373    cx.update_editor(|editor, window, cx| {
25374        editor.newline(&Newline, window, cx);
25375    });
25376    cx.run_until_parked();
25377    cx.assert_editor_state(indoc! {"
25378
25379        if [ \"$1\" = \"test\" ]; then
25380            ˇ
25381    "});
25382
25383    // test correct indent after newline after `else`
25384    cx.set_state(indoc! {"
25385        if [ \"$1\" = \"test\" ]; then
25386        elseˇ
25387    "});
25388    cx.update_editor(|editor, window, cx| {
25389        editor.newline(&Newline, window, cx);
25390    });
25391    cx.run_until_parked();
25392    cx.assert_editor_state(indoc! {"
25393        if [ \"$1\" = \"test\" ]; then
25394        else
25395            ˇ
25396    "});
25397
25398    // test correct indent after newline after `elif`
25399    cx.set_state(indoc! {"
25400        if [ \"$1\" = \"test\" ]; then
25401        elifˇ
25402    "});
25403    cx.update_editor(|editor, window, cx| {
25404        editor.newline(&Newline, window, cx);
25405    });
25406    cx.run_until_parked();
25407    cx.assert_editor_state(indoc! {"
25408        if [ \"$1\" = \"test\" ]; then
25409        elif
25410            ˇ
25411    "});
25412
25413    // test correct indent after newline after `do`
25414    cx.set_state(indoc! {"
25415        for file in *.txt; doˇ
25416    "});
25417    cx.update_editor(|editor, window, cx| {
25418        editor.newline(&Newline, window, cx);
25419    });
25420    cx.run_until_parked();
25421    cx.assert_editor_state(indoc! {"
25422        for file in *.txt; do
25423            ˇ
25424    "});
25425
25426    // test correct indent after newline after case pattern
25427    cx.set_state(indoc! {"
25428        case \"$1\" in
25429            start)ˇ
25430    "});
25431    cx.update_editor(|editor, window, cx| {
25432        editor.newline(&Newline, window, cx);
25433    });
25434    cx.run_until_parked();
25435    cx.assert_editor_state(indoc! {"
25436        case \"$1\" in
25437            start)
25438                ˇ
25439    "});
25440
25441    // test correct indent after newline after case pattern
25442    cx.set_state(indoc! {"
25443        case \"$1\" in
25444            start)
25445                ;;
25446            *)ˇ
25447    "});
25448    cx.update_editor(|editor, window, cx| {
25449        editor.newline(&Newline, window, cx);
25450    });
25451    cx.run_until_parked();
25452    cx.assert_editor_state(indoc! {"
25453        case \"$1\" in
25454            start)
25455                ;;
25456            *)
25457                ˇ
25458    "});
25459
25460    // test correct indent after newline after function opening brace
25461    cx.set_state(indoc! {"
25462        function test() {ˇ}
25463    "});
25464    cx.update_editor(|editor, window, cx| {
25465        editor.newline(&Newline, window, cx);
25466    });
25467    cx.run_until_parked();
25468    cx.assert_editor_state(indoc! {"
25469        function test() {
25470            ˇ
25471        }
25472    "});
25473
25474    // test no extra indent after semicolon on same line
25475    cx.set_state(indoc! {"
25476        echo \"test\"25477    "});
25478    cx.update_editor(|editor, window, cx| {
25479        editor.newline(&Newline, window, cx);
25480    });
25481    cx.run_until_parked();
25482    cx.assert_editor_state(indoc! {"
25483        echo \"test\";
25484        ˇ
25485    "});
25486}
25487
25488fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
25489    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
25490    point..point
25491}
25492
25493#[track_caller]
25494fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
25495    let (text, ranges) = marked_text_ranges(marked_text, true);
25496    assert_eq!(editor.text(cx), text);
25497    assert_eq!(
25498        editor.selections.ranges(&editor.display_snapshot(cx)),
25499        ranges,
25500        "Assert selections are {}",
25501        marked_text
25502    );
25503}
25504
25505pub fn handle_signature_help_request(
25506    cx: &mut EditorLspTestContext,
25507    mocked_response: lsp::SignatureHelp,
25508) -> impl Future<Output = ()> + use<> {
25509    let mut request =
25510        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
25511            let mocked_response = mocked_response.clone();
25512            async move { Ok(Some(mocked_response)) }
25513        });
25514
25515    async move {
25516        request.next().await;
25517    }
25518}
25519
25520#[track_caller]
25521pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
25522    cx.update_editor(|editor, _, _| {
25523        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
25524            let entries = menu.entries.borrow();
25525            let entries = entries
25526                .iter()
25527                .map(|entry| entry.string.as_str())
25528                .collect::<Vec<_>>();
25529            assert_eq!(entries, expected);
25530        } else {
25531            panic!("Expected completions menu");
25532        }
25533    });
25534}
25535
25536/// Handle completion request passing a marked string specifying where the completion
25537/// should be triggered from using '|' character, what range should be replaced, and what completions
25538/// should be returned using '<' and '>' to delimit the range.
25539///
25540/// Also see `handle_completion_request_with_insert_and_replace`.
25541#[track_caller]
25542pub fn handle_completion_request(
25543    marked_string: &str,
25544    completions: Vec<&'static str>,
25545    is_incomplete: bool,
25546    counter: Arc<AtomicUsize>,
25547    cx: &mut EditorLspTestContext,
25548) -> impl Future<Output = ()> {
25549    let complete_from_marker: TextRangeMarker = '|'.into();
25550    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25551    let (_, mut marked_ranges) = marked_text_ranges_by(
25552        marked_string,
25553        vec![complete_from_marker.clone(), replace_range_marker.clone()],
25554    );
25555
25556    let complete_from_position =
25557        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25558    let replace_range =
25559        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25560
25561    let mut request =
25562        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25563            let completions = completions.clone();
25564            counter.fetch_add(1, atomic::Ordering::Release);
25565            async move {
25566                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25567                assert_eq!(
25568                    params.text_document_position.position,
25569                    complete_from_position
25570                );
25571                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
25572                    is_incomplete,
25573                    item_defaults: None,
25574                    items: completions
25575                        .iter()
25576                        .map(|completion_text| lsp::CompletionItem {
25577                            label: completion_text.to_string(),
25578                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
25579                                range: replace_range,
25580                                new_text: completion_text.to_string(),
25581                            })),
25582                            ..Default::default()
25583                        })
25584                        .collect(),
25585                })))
25586            }
25587        });
25588
25589    async move {
25590        request.next().await;
25591    }
25592}
25593
25594/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
25595/// given instead, which also contains an `insert` range.
25596///
25597/// This function uses markers to define ranges:
25598/// - `|` marks the cursor position
25599/// - `<>` marks the replace range
25600/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
25601pub fn handle_completion_request_with_insert_and_replace(
25602    cx: &mut EditorLspTestContext,
25603    marked_string: &str,
25604    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
25605    counter: Arc<AtomicUsize>,
25606) -> impl Future<Output = ()> {
25607    let complete_from_marker: TextRangeMarker = '|'.into();
25608    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25609    let insert_range_marker: TextRangeMarker = ('{', '}').into();
25610
25611    let (_, mut marked_ranges) = marked_text_ranges_by(
25612        marked_string,
25613        vec![
25614            complete_from_marker.clone(),
25615            replace_range_marker.clone(),
25616            insert_range_marker.clone(),
25617        ],
25618    );
25619
25620    let complete_from_position =
25621        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25622    let replace_range =
25623        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25624
25625    let insert_range = match marked_ranges.remove(&insert_range_marker) {
25626        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
25627        _ => lsp::Range {
25628            start: replace_range.start,
25629            end: complete_from_position,
25630        },
25631    };
25632
25633    let mut request =
25634        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25635            let completions = completions.clone();
25636            counter.fetch_add(1, atomic::Ordering::Release);
25637            async move {
25638                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25639                assert_eq!(
25640                    params.text_document_position.position, complete_from_position,
25641                    "marker `|` position doesn't match",
25642                );
25643                Ok(Some(lsp::CompletionResponse::Array(
25644                    completions
25645                        .iter()
25646                        .map(|(label, new_text)| lsp::CompletionItem {
25647                            label: label.to_string(),
25648                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
25649                                lsp::InsertReplaceEdit {
25650                                    insert: insert_range,
25651                                    replace: replace_range,
25652                                    new_text: new_text.to_string(),
25653                                },
25654                            )),
25655                            ..Default::default()
25656                        })
25657                        .collect(),
25658                )))
25659            }
25660        });
25661
25662    async move {
25663        request.next().await;
25664    }
25665}
25666
25667fn handle_resolve_completion_request(
25668    cx: &mut EditorLspTestContext,
25669    edits: Option<Vec<(&'static str, &'static str)>>,
25670) -> impl Future<Output = ()> {
25671    let edits = edits.map(|edits| {
25672        edits
25673            .iter()
25674            .map(|(marked_string, new_text)| {
25675                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
25676                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
25677                lsp::TextEdit::new(replace_range, new_text.to_string())
25678            })
25679            .collect::<Vec<_>>()
25680    });
25681
25682    let mut request =
25683        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
25684            let edits = edits.clone();
25685            async move {
25686                Ok(lsp::CompletionItem {
25687                    additional_text_edits: edits,
25688                    ..Default::default()
25689                })
25690            }
25691        });
25692
25693    async move {
25694        request.next().await;
25695    }
25696}
25697
25698pub(crate) fn update_test_language_settings(
25699    cx: &mut TestAppContext,
25700    f: impl Fn(&mut AllLanguageSettingsContent),
25701) {
25702    cx.update(|cx| {
25703        SettingsStore::update_global(cx, |store, cx| {
25704            store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
25705        });
25706    });
25707}
25708
25709pub(crate) fn update_test_project_settings(
25710    cx: &mut TestAppContext,
25711    f: impl Fn(&mut ProjectSettingsContent),
25712) {
25713    cx.update(|cx| {
25714        SettingsStore::update_global(cx, |store, cx| {
25715            store.update_user_settings(cx, |settings| f(&mut settings.project));
25716        });
25717    });
25718}
25719
25720pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
25721    cx.update(|cx| {
25722        assets::Assets.load_test_fonts(cx);
25723        let store = SettingsStore::test(cx);
25724        cx.set_global(store);
25725        theme::init(theme::LoadThemes::JustBase, cx);
25726        release_channel::init(SemanticVersion::default(), cx);
25727        crate::init(cx);
25728    });
25729    zlog::init_test();
25730    update_test_language_settings(cx, f);
25731}
25732
25733#[track_caller]
25734fn assert_hunk_revert(
25735    not_reverted_text_with_selections: &str,
25736    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
25737    expected_reverted_text_with_selections: &str,
25738    base_text: &str,
25739    cx: &mut EditorLspTestContext,
25740) {
25741    cx.set_state(not_reverted_text_with_selections);
25742    cx.set_head_text(base_text);
25743    cx.executor().run_until_parked();
25744
25745    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
25746        let snapshot = editor.snapshot(window, cx);
25747        let reverted_hunk_statuses = snapshot
25748            .buffer_snapshot()
25749            .diff_hunks_in_range(0..snapshot.buffer_snapshot().len())
25750            .map(|hunk| hunk.status().kind)
25751            .collect::<Vec<_>>();
25752
25753        editor.git_restore(&Default::default(), window, cx);
25754        reverted_hunk_statuses
25755    });
25756    cx.executor().run_until_parked();
25757    cx.assert_editor_state(expected_reverted_text_with_selections);
25758    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
25759}
25760
25761#[gpui::test(iterations = 10)]
25762async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
25763    init_test(cx, |_| {});
25764
25765    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
25766    let counter = diagnostic_requests.clone();
25767
25768    let fs = FakeFs::new(cx.executor());
25769    fs.insert_tree(
25770        path!("/a"),
25771        json!({
25772            "first.rs": "fn main() { let a = 5; }",
25773            "second.rs": "// Test file",
25774        }),
25775    )
25776    .await;
25777
25778    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25779    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25780    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25781
25782    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25783    language_registry.add(rust_lang());
25784    let mut fake_servers = language_registry.register_fake_lsp(
25785        "Rust",
25786        FakeLspAdapter {
25787            capabilities: lsp::ServerCapabilities {
25788                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
25789                    lsp::DiagnosticOptions {
25790                        identifier: None,
25791                        inter_file_dependencies: true,
25792                        workspace_diagnostics: true,
25793                        work_done_progress_options: Default::default(),
25794                    },
25795                )),
25796                ..Default::default()
25797            },
25798            ..Default::default()
25799        },
25800    );
25801
25802    let editor = workspace
25803        .update(cx, |workspace, window, cx| {
25804            workspace.open_abs_path(
25805                PathBuf::from(path!("/a/first.rs")),
25806                OpenOptions::default(),
25807                window,
25808                cx,
25809            )
25810        })
25811        .unwrap()
25812        .await
25813        .unwrap()
25814        .downcast::<Editor>()
25815        .unwrap();
25816    let fake_server = fake_servers.next().await.unwrap();
25817    let server_id = fake_server.server.server_id();
25818    let mut first_request = fake_server
25819        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
25820            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
25821            let result_id = Some(new_result_id.to_string());
25822            assert_eq!(
25823                params.text_document.uri,
25824                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25825            );
25826            async move {
25827                Ok(lsp::DocumentDiagnosticReportResult::Report(
25828                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
25829                        related_documents: None,
25830                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
25831                            items: Vec::new(),
25832                            result_id,
25833                        },
25834                    }),
25835                ))
25836            }
25837        });
25838
25839    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
25840        project.update(cx, |project, cx| {
25841            let buffer_id = editor
25842                .read(cx)
25843                .buffer()
25844                .read(cx)
25845                .as_singleton()
25846                .expect("created a singleton buffer")
25847                .read(cx)
25848                .remote_id();
25849            let buffer_result_id = project
25850                .lsp_store()
25851                .read(cx)
25852                .result_id(server_id, buffer_id, cx);
25853            assert_eq!(expected, buffer_result_id);
25854        });
25855    };
25856
25857    ensure_result_id(None, cx);
25858    cx.executor().advance_clock(Duration::from_millis(60));
25859    cx.executor().run_until_parked();
25860    assert_eq!(
25861        diagnostic_requests.load(atomic::Ordering::Acquire),
25862        1,
25863        "Opening file should trigger diagnostic request"
25864    );
25865    first_request
25866        .next()
25867        .await
25868        .expect("should have sent the first diagnostics pull request");
25869    ensure_result_id(Some("1".to_string()), cx);
25870
25871    // Editing should trigger diagnostics
25872    editor.update_in(cx, |editor, window, cx| {
25873        editor.handle_input("2", window, cx)
25874    });
25875    cx.executor().advance_clock(Duration::from_millis(60));
25876    cx.executor().run_until_parked();
25877    assert_eq!(
25878        diagnostic_requests.load(atomic::Ordering::Acquire),
25879        2,
25880        "Editing should trigger diagnostic request"
25881    );
25882    ensure_result_id(Some("2".to_string()), cx);
25883
25884    // Moving cursor should not trigger diagnostic request
25885    editor.update_in(cx, |editor, window, cx| {
25886        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25887            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
25888        });
25889    });
25890    cx.executor().advance_clock(Duration::from_millis(60));
25891    cx.executor().run_until_parked();
25892    assert_eq!(
25893        diagnostic_requests.load(atomic::Ordering::Acquire),
25894        2,
25895        "Cursor movement should not trigger diagnostic request"
25896    );
25897    ensure_result_id(Some("2".to_string()), cx);
25898    // Multiple rapid edits should be debounced
25899    for _ in 0..5 {
25900        editor.update_in(cx, |editor, window, cx| {
25901            editor.handle_input("x", window, cx)
25902        });
25903    }
25904    cx.executor().advance_clock(Duration::from_millis(60));
25905    cx.executor().run_until_parked();
25906
25907    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
25908    assert!(
25909        final_requests <= 4,
25910        "Multiple rapid edits should be debounced (got {final_requests} requests)",
25911    );
25912    ensure_result_id(Some(final_requests.to_string()), cx);
25913}
25914
25915#[gpui::test]
25916async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
25917    // Regression test for issue #11671
25918    // Previously, adding a cursor after moving multiple cursors would reset
25919    // the cursor count instead of adding to the existing cursors.
25920    init_test(cx, |_| {});
25921    let mut cx = EditorTestContext::new(cx).await;
25922
25923    // Create a simple buffer with cursor at start
25924    cx.set_state(indoc! {"
25925        ˇaaaa
25926        bbbb
25927        cccc
25928        dddd
25929        eeee
25930        ffff
25931        gggg
25932        hhhh"});
25933
25934    // Add 2 cursors below (so we have 3 total)
25935    cx.update_editor(|editor, window, cx| {
25936        editor.add_selection_below(&Default::default(), window, cx);
25937        editor.add_selection_below(&Default::default(), window, cx);
25938    });
25939
25940    // Verify we have 3 cursors
25941    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
25942    assert_eq!(
25943        initial_count, 3,
25944        "Should have 3 cursors after adding 2 below"
25945    );
25946
25947    // Move down one line
25948    cx.update_editor(|editor, window, cx| {
25949        editor.move_down(&MoveDown, window, cx);
25950    });
25951
25952    // Add another cursor below
25953    cx.update_editor(|editor, window, cx| {
25954        editor.add_selection_below(&Default::default(), window, cx);
25955    });
25956
25957    // Should now have 4 cursors (3 original + 1 new)
25958    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
25959    assert_eq!(
25960        final_count, 4,
25961        "Should have 4 cursors after moving and adding another"
25962    );
25963}
25964
25965#[gpui::test]
25966async fn test_add_selection_skip_soft_wrap_option(cx: &mut TestAppContext) {
25967    init_test(cx, |_| {});
25968
25969    let mut cx = EditorTestContext::new(cx).await;
25970
25971    cx.set_state(indoc!(
25972        r#"ˇThis is a very long line that will be wrapped when soft wrapping is enabled
25973           Second line here"#
25974    ));
25975
25976    cx.update_editor(|editor, window, cx| {
25977        // Enable soft wrapping with a narrow width to force soft wrapping and
25978        // confirm that more than 2 rows are being displayed.
25979        editor.set_wrap_width(Some(100.0.into()), cx);
25980        assert!(editor.display_text(cx).lines().count() > 2);
25981
25982        editor.add_selection_below(
25983            &AddSelectionBelow {
25984                skip_soft_wrap: true,
25985            },
25986            window,
25987            cx,
25988        );
25989
25990        assert_eq!(
25991            display_ranges(editor, cx),
25992            &[
25993                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
25994                DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(8), 0),
25995            ]
25996        );
25997
25998        editor.add_selection_above(
25999            &AddSelectionAbove {
26000                skip_soft_wrap: true,
26001            },
26002            window,
26003            cx,
26004        );
26005
26006        assert_eq!(
26007            display_ranges(editor, cx),
26008            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
26009        );
26010
26011        editor.add_selection_below(
26012            &AddSelectionBelow {
26013                skip_soft_wrap: false,
26014            },
26015            window,
26016            cx,
26017        );
26018
26019        assert_eq!(
26020            display_ranges(editor, cx),
26021            &[
26022                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
26023                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
26024            ]
26025        );
26026
26027        editor.add_selection_above(
26028            &AddSelectionAbove {
26029                skip_soft_wrap: false,
26030            },
26031            window,
26032            cx,
26033        );
26034
26035        assert_eq!(
26036            display_ranges(editor, cx),
26037            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
26038        );
26039    });
26040}
26041
26042#[gpui::test(iterations = 10)]
26043async fn test_document_colors(cx: &mut TestAppContext) {
26044    let expected_color = Rgba {
26045        r: 0.33,
26046        g: 0.33,
26047        b: 0.33,
26048        a: 0.33,
26049    };
26050
26051    init_test(cx, |_| {});
26052
26053    let fs = FakeFs::new(cx.executor());
26054    fs.insert_tree(
26055        path!("/a"),
26056        json!({
26057            "first.rs": "fn main() { let a = 5; }",
26058        }),
26059    )
26060    .await;
26061
26062    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
26063    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
26064    let cx = &mut VisualTestContext::from_window(*workspace, cx);
26065
26066    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26067    language_registry.add(rust_lang());
26068    let mut fake_servers = language_registry.register_fake_lsp(
26069        "Rust",
26070        FakeLspAdapter {
26071            capabilities: lsp::ServerCapabilities {
26072                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
26073                ..lsp::ServerCapabilities::default()
26074            },
26075            name: "rust-analyzer",
26076            ..FakeLspAdapter::default()
26077        },
26078    );
26079    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
26080        "Rust",
26081        FakeLspAdapter {
26082            capabilities: lsp::ServerCapabilities {
26083                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
26084                ..lsp::ServerCapabilities::default()
26085            },
26086            name: "not-rust-analyzer",
26087            ..FakeLspAdapter::default()
26088        },
26089    );
26090
26091    let editor = workspace
26092        .update(cx, |workspace, window, cx| {
26093            workspace.open_abs_path(
26094                PathBuf::from(path!("/a/first.rs")),
26095                OpenOptions::default(),
26096                window,
26097                cx,
26098            )
26099        })
26100        .unwrap()
26101        .await
26102        .unwrap()
26103        .downcast::<Editor>()
26104        .unwrap();
26105    let fake_language_server = fake_servers.next().await.unwrap();
26106    let fake_language_server_without_capabilities =
26107        fake_servers_without_capabilities.next().await.unwrap();
26108    let requests_made = Arc::new(AtomicUsize::new(0));
26109    let closure_requests_made = Arc::clone(&requests_made);
26110    let mut color_request_handle = fake_language_server
26111        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
26112            let requests_made = Arc::clone(&closure_requests_made);
26113            async move {
26114                assert_eq!(
26115                    params.text_document.uri,
26116                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
26117                );
26118                requests_made.fetch_add(1, atomic::Ordering::Release);
26119                Ok(vec![
26120                    lsp::ColorInformation {
26121                        range: lsp::Range {
26122                            start: lsp::Position {
26123                                line: 0,
26124                                character: 0,
26125                            },
26126                            end: lsp::Position {
26127                                line: 0,
26128                                character: 1,
26129                            },
26130                        },
26131                        color: lsp::Color {
26132                            red: 0.33,
26133                            green: 0.33,
26134                            blue: 0.33,
26135                            alpha: 0.33,
26136                        },
26137                    },
26138                    lsp::ColorInformation {
26139                        range: lsp::Range {
26140                            start: lsp::Position {
26141                                line: 0,
26142                                character: 0,
26143                            },
26144                            end: lsp::Position {
26145                                line: 0,
26146                                character: 1,
26147                            },
26148                        },
26149                        color: lsp::Color {
26150                            red: 0.33,
26151                            green: 0.33,
26152                            blue: 0.33,
26153                            alpha: 0.33,
26154                        },
26155                    },
26156                ])
26157            }
26158        });
26159
26160    let _handle = fake_language_server_without_capabilities
26161        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
26162            panic!("Should not be called");
26163        });
26164    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26165    color_request_handle.next().await.unwrap();
26166    cx.run_until_parked();
26167    assert_eq!(
26168        1,
26169        requests_made.load(atomic::Ordering::Acquire),
26170        "Should query for colors once per editor open"
26171    );
26172    editor.update_in(cx, |editor, _, cx| {
26173        assert_eq!(
26174            vec![expected_color],
26175            extract_color_inlays(editor, cx),
26176            "Should have an initial inlay"
26177        );
26178    });
26179
26180    // opening another file in a split should not influence the LSP query counter
26181    workspace
26182        .update(cx, |workspace, window, cx| {
26183            assert_eq!(
26184                workspace.panes().len(),
26185                1,
26186                "Should have one pane with one editor"
26187            );
26188            workspace.move_item_to_pane_in_direction(
26189                &MoveItemToPaneInDirection {
26190                    direction: SplitDirection::Right,
26191                    focus: false,
26192                    clone: true,
26193                },
26194                window,
26195                cx,
26196            );
26197        })
26198        .unwrap();
26199    cx.run_until_parked();
26200    workspace
26201        .update(cx, |workspace, _, cx| {
26202            let panes = workspace.panes();
26203            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
26204            for pane in panes {
26205                let editor = pane
26206                    .read(cx)
26207                    .active_item()
26208                    .and_then(|item| item.downcast::<Editor>())
26209                    .expect("Should have opened an editor in each split");
26210                let editor_file = editor
26211                    .read(cx)
26212                    .buffer()
26213                    .read(cx)
26214                    .as_singleton()
26215                    .expect("test deals with singleton buffers")
26216                    .read(cx)
26217                    .file()
26218                    .expect("test buffese should have a file")
26219                    .path();
26220                assert_eq!(
26221                    editor_file.as_ref(),
26222                    rel_path("first.rs"),
26223                    "Both editors should be opened for the same file"
26224                )
26225            }
26226        })
26227        .unwrap();
26228
26229    cx.executor().advance_clock(Duration::from_millis(500));
26230    let save = editor.update_in(cx, |editor, window, cx| {
26231        editor.move_to_end(&MoveToEnd, window, cx);
26232        editor.handle_input("dirty", window, cx);
26233        editor.save(
26234            SaveOptions {
26235                format: true,
26236                autosave: true,
26237            },
26238            project.clone(),
26239            window,
26240            cx,
26241        )
26242    });
26243    save.await.unwrap();
26244
26245    color_request_handle.next().await.unwrap();
26246    cx.run_until_parked();
26247    assert_eq!(
26248        2,
26249        requests_made.load(atomic::Ordering::Acquire),
26250        "Should query for colors once per save (deduplicated) and once per formatting after save"
26251    );
26252
26253    drop(editor);
26254    let close = workspace
26255        .update(cx, |workspace, window, cx| {
26256            workspace.active_pane().update(cx, |pane, cx| {
26257                pane.close_active_item(&CloseActiveItem::default(), window, cx)
26258            })
26259        })
26260        .unwrap();
26261    close.await.unwrap();
26262    let close = workspace
26263        .update(cx, |workspace, window, cx| {
26264            workspace.active_pane().update(cx, |pane, cx| {
26265                pane.close_active_item(&CloseActiveItem::default(), window, cx)
26266            })
26267        })
26268        .unwrap();
26269    close.await.unwrap();
26270    assert_eq!(
26271        2,
26272        requests_made.load(atomic::Ordering::Acquire),
26273        "After saving and closing all editors, no extra requests should be made"
26274    );
26275    workspace
26276        .update(cx, |workspace, _, cx| {
26277            assert!(
26278                workspace.active_item(cx).is_none(),
26279                "Should close all editors"
26280            )
26281        })
26282        .unwrap();
26283
26284    workspace
26285        .update(cx, |workspace, window, cx| {
26286            workspace.active_pane().update(cx, |pane, cx| {
26287                pane.navigate_backward(&workspace::GoBack, window, cx);
26288            })
26289        })
26290        .unwrap();
26291    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26292    cx.run_until_parked();
26293    let editor = workspace
26294        .update(cx, |workspace, _, cx| {
26295            workspace
26296                .active_item(cx)
26297                .expect("Should have reopened the editor again after navigating back")
26298                .downcast::<Editor>()
26299                .expect("Should be an editor")
26300        })
26301        .unwrap();
26302
26303    assert_eq!(
26304        2,
26305        requests_made.load(atomic::Ordering::Acquire),
26306        "Cache should be reused on buffer close and reopen"
26307    );
26308    editor.update(cx, |editor, cx| {
26309        assert_eq!(
26310            vec![expected_color],
26311            extract_color_inlays(editor, cx),
26312            "Should have an initial inlay"
26313        );
26314    });
26315
26316    drop(color_request_handle);
26317    let closure_requests_made = Arc::clone(&requests_made);
26318    let mut empty_color_request_handle = fake_language_server
26319        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
26320            let requests_made = Arc::clone(&closure_requests_made);
26321            async move {
26322                assert_eq!(
26323                    params.text_document.uri,
26324                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
26325                );
26326                requests_made.fetch_add(1, atomic::Ordering::Release);
26327                Ok(Vec::new())
26328            }
26329        });
26330    let save = editor.update_in(cx, |editor, window, cx| {
26331        editor.move_to_end(&MoveToEnd, window, cx);
26332        editor.handle_input("dirty_again", window, cx);
26333        editor.save(
26334            SaveOptions {
26335                format: false,
26336                autosave: true,
26337            },
26338            project.clone(),
26339            window,
26340            cx,
26341        )
26342    });
26343    save.await.unwrap();
26344
26345    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26346    empty_color_request_handle.next().await.unwrap();
26347    cx.run_until_parked();
26348    assert_eq!(
26349        3,
26350        requests_made.load(atomic::Ordering::Acquire),
26351        "Should query for colors once per save only, as formatting was not requested"
26352    );
26353    editor.update(cx, |editor, cx| {
26354        assert_eq!(
26355            Vec::<Rgba>::new(),
26356            extract_color_inlays(editor, cx),
26357            "Should clear all colors when the server returns an empty response"
26358        );
26359    });
26360}
26361
26362#[gpui::test]
26363async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
26364    init_test(cx, |_| {});
26365    let (editor, cx) = cx.add_window_view(Editor::single_line);
26366    editor.update_in(cx, |editor, window, cx| {
26367        editor.set_text("oops\n\nwow\n", window, cx)
26368    });
26369    cx.run_until_parked();
26370    editor.update(cx, |editor, cx| {
26371        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
26372    });
26373    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
26374    cx.run_until_parked();
26375    editor.update(cx, |editor, cx| {
26376        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
26377    });
26378}
26379
26380#[gpui::test]
26381async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
26382    init_test(cx, |_| {});
26383
26384    cx.update(|cx| {
26385        register_project_item::<Editor>(cx);
26386    });
26387
26388    let fs = FakeFs::new(cx.executor());
26389    fs.insert_tree("/root1", json!({})).await;
26390    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
26391        .await;
26392
26393    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
26394    let (workspace, cx) =
26395        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
26396
26397    let worktree_id = project.update(cx, |project, cx| {
26398        project.worktrees(cx).next().unwrap().read(cx).id()
26399    });
26400
26401    let handle = workspace
26402        .update_in(cx, |workspace, window, cx| {
26403            let project_path = (worktree_id, rel_path("one.pdf"));
26404            workspace.open_path(project_path, None, true, window, cx)
26405        })
26406        .await
26407        .unwrap();
26408
26409    assert_eq!(
26410        handle.to_any().entity_type(),
26411        TypeId::of::<InvalidItemView>()
26412    );
26413}
26414
26415#[gpui::test]
26416async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
26417    init_test(cx, |_| {});
26418
26419    let language = Arc::new(Language::new(
26420        LanguageConfig::default(),
26421        Some(tree_sitter_rust::LANGUAGE.into()),
26422    ));
26423
26424    // Test hierarchical sibling navigation
26425    let text = r#"
26426        fn outer() {
26427            if condition {
26428                let a = 1;
26429            }
26430            let b = 2;
26431        }
26432
26433        fn another() {
26434            let c = 3;
26435        }
26436    "#;
26437
26438    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
26439    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
26440    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
26441
26442    // Wait for parsing to complete
26443    editor
26444        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
26445        .await;
26446
26447    editor.update_in(cx, |editor, window, cx| {
26448        // Start by selecting "let a = 1;" inside the if block
26449        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26450            s.select_display_ranges([
26451                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
26452            ]);
26453        });
26454
26455        let initial_selection = editor
26456            .selections
26457            .display_ranges(&editor.display_snapshot(cx));
26458        assert_eq!(initial_selection.len(), 1, "Should have one selection");
26459
26460        // Test select next sibling - should move up levels to find the next sibling
26461        // Since "let a = 1;" has no siblings in the if block, it should move up
26462        // to find "let b = 2;" which is a sibling of the if block
26463        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
26464        let next_selection = editor
26465            .selections
26466            .display_ranges(&editor.display_snapshot(cx));
26467
26468        // Should have a selection and it should be different from the initial
26469        assert_eq!(
26470            next_selection.len(),
26471            1,
26472            "Should have one selection after next"
26473        );
26474        assert_ne!(
26475            next_selection[0], initial_selection[0],
26476            "Next sibling selection should be different"
26477        );
26478
26479        // Test hierarchical navigation by going to the end of the current function
26480        // and trying to navigate to the next function
26481        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26482            s.select_display_ranges([
26483                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
26484            ]);
26485        });
26486
26487        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
26488        let function_next_selection = editor
26489            .selections
26490            .display_ranges(&editor.display_snapshot(cx));
26491
26492        // Should move to the next function
26493        assert_eq!(
26494            function_next_selection.len(),
26495            1,
26496            "Should have one selection after function next"
26497        );
26498
26499        // Test select previous sibling navigation
26500        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
26501        let prev_selection = editor
26502            .selections
26503            .display_ranges(&editor.display_snapshot(cx));
26504
26505        // Should have a selection and it should be different
26506        assert_eq!(
26507            prev_selection.len(),
26508            1,
26509            "Should have one selection after prev"
26510        );
26511        assert_ne!(
26512            prev_selection[0], function_next_selection[0],
26513            "Previous sibling selection should be different from next"
26514        );
26515    });
26516}
26517
26518#[gpui::test]
26519async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
26520    init_test(cx, |_| {});
26521
26522    let mut cx = EditorTestContext::new(cx).await;
26523    cx.set_state(
26524        "let ˇvariable = 42;
26525let another = variable + 1;
26526let result = variable * 2;",
26527    );
26528
26529    // Set up document highlights manually (simulating LSP response)
26530    cx.update_editor(|editor, _window, cx| {
26531        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
26532
26533        // Create highlights for "variable" occurrences
26534        let highlight_ranges = [
26535            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
26536            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
26537            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
26538        ];
26539
26540        let anchor_ranges: Vec<_> = highlight_ranges
26541            .iter()
26542            .map(|range| range.clone().to_anchors(&buffer_snapshot))
26543            .collect();
26544
26545        editor.highlight_background::<DocumentHighlightRead>(
26546            &anchor_ranges,
26547            |theme| theme.colors().editor_document_highlight_read_background,
26548            cx,
26549        );
26550    });
26551
26552    // Go to next highlight - should move to second "variable"
26553    cx.update_editor(|editor, window, cx| {
26554        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26555    });
26556    cx.assert_editor_state(
26557        "let variable = 42;
26558let another = ˇvariable + 1;
26559let result = variable * 2;",
26560    );
26561
26562    // Go to next highlight - should move to third "variable"
26563    cx.update_editor(|editor, window, cx| {
26564        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26565    });
26566    cx.assert_editor_state(
26567        "let variable = 42;
26568let another = variable + 1;
26569let result = ˇvariable * 2;",
26570    );
26571
26572    // Go to next highlight - should stay at third "variable" (no wrap-around)
26573    cx.update_editor(|editor, window, cx| {
26574        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26575    });
26576    cx.assert_editor_state(
26577        "let variable = 42;
26578let another = variable + 1;
26579let result = ˇvariable * 2;",
26580    );
26581
26582    // Now test going backwards from third position
26583    cx.update_editor(|editor, window, cx| {
26584        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26585    });
26586    cx.assert_editor_state(
26587        "let variable = 42;
26588let another = ˇvariable + 1;
26589let result = variable * 2;",
26590    );
26591
26592    // Go to previous highlight - should move to first "variable"
26593    cx.update_editor(|editor, window, cx| {
26594        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26595    });
26596    cx.assert_editor_state(
26597        "let ˇvariable = 42;
26598let another = variable + 1;
26599let result = variable * 2;",
26600    );
26601
26602    // Go to previous highlight - should stay on first "variable"
26603    cx.update_editor(|editor, window, cx| {
26604        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26605    });
26606    cx.assert_editor_state(
26607        "let ˇvariable = 42;
26608let another = variable + 1;
26609let result = variable * 2;",
26610    );
26611}
26612
26613#[gpui::test]
26614async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
26615    cx: &mut gpui::TestAppContext,
26616) {
26617    init_test(cx, |_| {});
26618
26619    let url = "https://zed.dev";
26620
26621    let markdown_language = Arc::new(Language::new(
26622        LanguageConfig {
26623            name: "Markdown".into(),
26624            ..LanguageConfig::default()
26625        },
26626        None,
26627    ));
26628
26629    let mut cx = EditorTestContext::new(cx).await;
26630    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26631    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
26632
26633    cx.update_editor(|editor, window, cx| {
26634        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26635        editor.paste(&Paste, window, cx);
26636    });
26637
26638    cx.assert_editor_state(&format!(
26639        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)"
26640    ));
26641}
26642
26643#[gpui::test]
26644async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
26645    cx: &mut gpui::TestAppContext,
26646) {
26647    init_test(cx, |_| {});
26648
26649    let url = "https://zed.dev";
26650
26651    let markdown_language = Arc::new(Language::new(
26652        LanguageConfig {
26653            name: "Markdown".into(),
26654            ..LanguageConfig::default()
26655        },
26656        None,
26657    ));
26658
26659    let mut cx = EditorTestContext::new(cx).await;
26660    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26661    cx.set_state(&format!(
26662        "Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»"
26663    ));
26664
26665    cx.update_editor(|editor, window, cx| {
26666        editor.copy(&Copy, window, cx);
26667    });
26668
26669    cx.set_state(&format!(
26670        "Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}"
26671    ));
26672
26673    cx.update_editor(|editor, window, cx| {
26674        editor.paste(&Paste, window, cx);
26675    });
26676
26677    cx.assert_editor_state(&format!(
26678        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}"
26679    ));
26680}
26681
26682#[gpui::test]
26683async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link(
26684    cx: &mut gpui::TestAppContext,
26685) {
26686    init_test(cx, |_| {});
26687
26688    let url = "https://zed.dev";
26689
26690    let markdown_language = Arc::new(Language::new(
26691        LanguageConfig {
26692            name: "Markdown".into(),
26693            ..LanguageConfig::default()
26694        },
26695        None,
26696    ));
26697
26698    let mut cx = EditorTestContext::new(cx).await;
26699    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26700    cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»");
26701
26702    cx.update_editor(|editor, window, cx| {
26703        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26704        editor.paste(&Paste, window, cx);
26705    });
26706
26707    cx.assert_editor_state(&format!("Please visit zed's homepage: {url}ˇ"));
26708}
26709
26710#[gpui::test]
26711async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link(
26712    cx: &mut gpui::TestAppContext,
26713) {
26714    init_test(cx, |_| {});
26715
26716    let text = "Awesome";
26717
26718    let markdown_language = Arc::new(Language::new(
26719        LanguageConfig {
26720            name: "Markdown".into(),
26721            ..LanguageConfig::default()
26722        },
26723        None,
26724    ));
26725
26726    let mut cx = EditorTestContext::new(cx).await;
26727    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26728    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»");
26729
26730    cx.update_editor(|editor, window, cx| {
26731        cx.write_to_clipboard(ClipboardItem::new_string(text.to_string()));
26732        editor.paste(&Paste, window, cx);
26733    });
26734
26735    cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ"));
26736}
26737
26738#[gpui::test]
26739async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language(
26740    cx: &mut gpui::TestAppContext,
26741) {
26742    init_test(cx, |_| {});
26743
26744    let url = "https://zed.dev";
26745
26746    let markdown_language = Arc::new(Language::new(
26747        LanguageConfig {
26748            name: "Rust".into(),
26749            ..LanguageConfig::default()
26750        },
26751        None,
26752    ));
26753
26754    let mut cx = EditorTestContext::new(cx).await;
26755    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26756    cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
26757
26758    cx.update_editor(|editor, window, cx| {
26759        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26760        editor.paste(&Paste, window, cx);
26761    });
26762
26763    cx.assert_editor_state(&format!(
26764        "// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)"
26765    ));
26766}
26767
26768#[gpui::test]
26769async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer(
26770    cx: &mut TestAppContext,
26771) {
26772    init_test(cx, |_| {});
26773
26774    let url = "https://zed.dev";
26775
26776    let markdown_language = Arc::new(Language::new(
26777        LanguageConfig {
26778            name: "Markdown".into(),
26779            ..LanguageConfig::default()
26780        },
26781        None,
26782    ));
26783
26784    let (editor, cx) = cx.add_window_view(|window, cx| {
26785        let multi_buffer = MultiBuffer::build_multi(
26786            [
26787                ("this will embed -> link", vec![Point::row_range(0..1)]),
26788                ("this will replace -> link", vec![Point::row_range(0..1)]),
26789            ],
26790            cx,
26791        );
26792        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
26793        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26794            s.select_ranges(vec![
26795                Point::new(0, 19)..Point::new(0, 23),
26796                Point::new(1, 21)..Point::new(1, 25),
26797            ])
26798        });
26799        let first_buffer_id = multi_buffer
26800            .read(cx)
26801            .excerpt_buffer_ids()
26802            .into_iter()
26803            .next()
26804            .unwrap();
26805        let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
26806        first_buffer.update(cx, |buffer, cx| {
26807            buffer.set_language(Some(markdown_language.clone()), cx);
26808        });
26809
26810        editor
26811    });
26812    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
26813
26814    cx.update_editor(|editor, window, cx| {
26815        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26816        editor.paste(&Paste, window, cx);
26817    });
26818
26819    cx.assert_editor_state(&format!(
26820        "this will embed -> [link]({url}\nthis will replace -> {url}ˇ"
26821    ));
26822}
26823
26824#[gpui::test]
26825async fn test_race_in_multibuffer_save(cx: &mut TestAppContext) {
26826    init_test(cx, |_| {});
26827
26828    let fs = FakeFs::new(cx.executor());
26829    fs.insert_tree(
26830        path!("/project"),
26831        json!({
26832            "first.rs": "# First Document\nSome content here.",
26833            "second.rs": "Plain text content for second file.",
26834        }),
26835    )
26836    .await;
26837
26838    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
26839    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
26840    let cx = &mut VisualTestContext::from_window(*workspace, cx);
26841
26842    let language = rust_lang();
26843    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26844    language_registry.add(language.clone());
26845    let mut fake_servers = language_registry.register_fake_lsp(
26846        "Rust",
26847        FakeLspAdapter {
26848            ..FakeLspAdapter::default()
26849        },
26850    );
26851
26852    let buffer1 = project
26853        .update(cx, |project, cx| {
26854            project.open_local_buffer(PathBuf::from(path!("/project/first.rs")), cx)
26855        })
26856        .await
26857        .unwrap();
26858    let buffer2 = project
26859        .update(cx, |project, cx| {
26860            project.open_local_buffer(PathBuf::from(path!("/project/second.rs")), cx)
26861        })
26862        .await
26863        .unwrap();
26864
26865    let multi_buffer = cx.new(|cx| {
26866        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
26867        multi_buffer.set_excerpts_for_path(
26868            PathKey::for_buffer(&buffer1, cx),
26869            buffer1.clone(),
26870            [Point::zero()..buffer1.read(cx).max_point()],
26871            3,
26872            cx,
26873        );
26874        multi_buffer.set_excerpts_for_path(
26875            PathKey::for_buffer(&buffer2, cx),
26876            buffer2.clone(),
26877            [Point::zero()..buffer1.read(cx).max_point()],
26878            3,
26879            cx,
26880        );
26881        multi_buffer
26882    });
26883
26884    let (editor, cx) = cx.add_window_view(|window, cx| {
26885        Editor::new(
26886            EditorMode::full(),
26887            multi_buffer,
26888            Some(project.clone()),
26889            window,
26890            cx,
26891        )
26892    });
26893
26894    let fake_language_server = fake_servers.next().await.unwrap();
26895
26896    buffer1.update(cx, |buffer, cx| buffer.edit([(0..0, "hello!")], None, cx));
26897
26898    let save = editor.update_in(cx, |editor, window, cx| {
26899        assert!(editor.is_dirty(cx));
26900
26901        editor.save(
26902            SaveOptions {
26903                format: true,
26904                autosave: true,
26905            },
26906            project,
26907            window,
26908            cx,
26909        )
26910    });
26911    let (start_edit_tx, start_edit_rx) = oneshot::channel();
26912    let (done_edit_tx, done_edit_rx) = oneshot::channel();
26913    let mut done_edit_rx = Some(done_edit_rx);
26914    let mut start_edit_tx = Some(start_edit_tx);
26915
26916    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| {
26917        start_edit_tx.take().unwrap().send(()).unwrap();
26918        let done_edit_rx = done_edit_rx.take().unwrap();
26919        async move {
26920            done_edit_rx.await.unwrap();
26921            Ok(None)
26922        }
26923    });
26924
26925    start_edit_rx.await.unwrap();
26926    buffer2
26927        .update(cx, |buffer, cx| buffer.edit([(0..0, "world!")], None, cx))
26928        .unwrap();
26929
26930    done_edit_tx.send(()).unwrap();
26931
26932    save.await.unwrap();
26933    cx.update(|_, cx| assert!(editor.is_dirty(cx)));
26934}
26935
26936#[track_caller]
26937fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
26938    editor
26939        .all_inlays(cx)
26940        .into_iter()
26941        .filter_map(|inlay| inlay.get_color())
26942        .map(Rgba::from)
26943        .collect()
26944}
26945
26946#[gpui::test]
26947fn test_duplicate_line_up_on_last_line_without_newline(cx: &mut TestAppContext) {
26948    init_test(cx, |_| {});
26949
26950    let editor = cx.add_window(|window, cx| {
26951        let buffer = MultiBuffer::build_simple("line1\nline2", cx);
26952        build_editor(buffer, window, cx)
26953    });
26954
26955    editor
26956        .update(cx, |editor, window, cx| {
26957            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26958                s.select_display_ranges([
26959                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
26960                ])
26961            });
26962
26963            editor.duplicate_line_up(&DuplicateLineUp, window, cx);
26964
26965            assert_eq!(
26966                editor.display_text(cx),
26967                "line1\nline2\nline2",
26968                "Duplicating last line upward should create duplicate above, not on same line"
26969            );
26970
26971            assert_eq!(
26972                editor
26973                    .selections
26974                    .display_ranges(&editor.display_snapshot(cx)),
26975                vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)],
26976                "Selection should move to the duplicated line"
26977            );
26978        })
26979        .unwrap();
26980}
26981
26982#[gpui::test]
26983async fn test_copy_line_without_trailing_newline(cx: &mut TestAppContext) {
26984    init_test(cx, |_| {});
26985
26986    let mut cx = EditorTestContext::new(cx).await;
26987
26988    cx.set_state("line1\nline2ˇ");
26989
26990    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
26991
26992    let clipboard_text = cx
26993        .read_from_clipboard()
26994        .and_then(|item| item.text().as_deref().map(str::to_string));
26995
26996    assert_eq!(
26997        clipboard_text,
26998        Some("line2\n".to_string()),
26999        "Copying a line without trailing newline should include a newline"
27000    );
27001
27002    cx.set_state("line1\nˇ");
27003
27004    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
27005
27006    cx.assert_editor_state("line1\nline2\nˇ");
27007}
27008
27009#[gpui::test]
27010async fn test_end_of_editor_context(cx: &mut TestAppContext) {
27011    init_test(cx, |_| {});
27012
27013    let mut cx = EditorTestContext::new(cx).await;
27014
27015    cx.set_state("line1\nline2ˇ");
27016    cx.update_editor(|e, window, cx| {
27017        e.set_mode(EditorMode::SingleLine);
27018        assert!(e.key_context(window, cx).contains("end_of_input"));
27019    });
27020    cx.set_state("ˇline1\nline2");
27021    cx.update_editor(|e, window, cx| {
27022        assert!(!e.key_context(window, cx).contains("end_of_input"));
27023    });
27024    cx.set_state("line1ˇ\nline2");
27025    cx.update_editor(|e, window, cx| {
27026        assert!(!e.key_context(window, cx).contains("end_of_input"));
27027    });
27028}
27029
27030#[gpui::test]
27031async fn test_sticky_scroll(cx: &mut TestAppContext) {
27032    init_test(cx, |_| {});
27033    let mut cx = EditorTestContext::new(cx).await;
27034
27035    let buffer = indoc! {"
27036            ˇfn foo() {
27037                let abc = 123;
27038            }
27039            struct Bar;
27040            impl Bar {
27041                fn new() -> Self {
27042                    Self
27043                }
27044            }
27045            fn baz() {
27046            }
27047        "};
27048    cx.set_state(&buffer);
27049
27050    cx.update_editor(|e, _, cx| {
27051        e.buffer()
27052            .read(cx)
27053            .as_singleton()
27054            .unwrap()
27055            .update(cx, |buffer, cx| {
27056                buffer.set_language(Some(rust_lang()), cx);
27057            })
27058    });
27059
27060    let mut sticky_headers = |offset: ScrollOffset| {
27061        cx.update_editor(|e, window, cx| {
27062            e.scroll(gpui::Point { x: 0., y: offset }, None, window, cx);
27063            EditorElement::sticky_headers(&e, &e.snapshot(window, cx), cx)
27064                .into_iter()
27065                .map(
27066                    |StickyHeader {
27067                         start_point,
27068                         offset,
27069                         ..
27070                     }| { (start_point, offset) },
27071                )
27072                .collect::<Vec<_>>()
27073        })
27074    };
27075
27076    let fn_foo = Point { row: 0, column: 0 };
27077    let impl_bar = Point { row: 4, column: 0 };
27078    let fn_new = Point { row: 5, column: 4 };
27079
27080    assert_eq!(sticky_headers(0.0), vec![]);
27081    assert_eq!(sticky_headers(0.5), vec![(fn_foo, 0.0)]);
27082    assert_eq!(sticky_headers(1.0), vec![(fn_foo, 0.0)]);
27083    assert_eq!(sticky_headers(1.5), vec![(fn_foo, -0.5)]);
27084    assert_eq!(sticky_headers(2.0), vec![]);
27085    assert_eq!(sticky_headers(2.5), vec![]);
27086    assert_eq!(sticky_headers(3.0), vec![]);
27087    assert_eq!(sticky_headers(3.5), vec![]);
27088    assert_eq!(sticky_headers(4.0), vec![]);
27089    assert_eq!(sticky_headers(4.5), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
27090    assert_eq!(sticky_headers(5.0), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
27091    assert_eq!(sticky_headers(5.5), vec![(impl_bar, 0.0), (fn_new, 0.5)]);
27092    assert_eq!(sticky_headers(6.0), vec![(impl_bar, 0.0)]);
27093    assert_eq!(sticky_headers(6.5), vec![(impl_bar, 0.0)]);
27094    assert_eq!(sticky_headers(7.0), vec![(impl_bar, 0.0)]);
27095    assert_eq!(sticky_headers(7.5), vec![(impl_bar, -0.5)]);
27096    assert_eq!(sticky_headers(8.0), vec![]);
27097    assert_eq!(sticky_headers(8.5), vec![]);
27098    assert_eq!(sticky_headers(9.0), vec![]);
27099    assert_eq!(sticky_headers(9.5), vec![]);
27100    assert_eq!(sticky_headers(10.0), vec![]);
27101}
27102
27103#[gpui::test]
27104async fn test_scroll_by_clicking_sticky_header(cx: &mut TestAppContext) {
27105    init_test(cx, |_| {});
27106    cx.update(|cx| {
27107        SettingsStore::update_global(cx, |store, cx| {
27108            store.update_user_settings(cx, |settings| {
27109                settings.editor.sticky_scroll = Some(settings::StickyScrollContent {
27110                    enabled: Some(true),
27111                })
27112            });
27113        });
27114    });
27115    let mut cx = EditorTestContext::new(cx).await;
27116
27117    let line_height = cx.editor(|editor, window, _cx| {
27118        editor
27119            .style()
27120            .unwrap()
27121            .text
27122            .line_height_in_pixels(window.rem_size())
27123    });
27124
27125    let buffer = indoc! {"
27126            ˇfn foo() {
27127                let abc = 123;
27128            }
27129            struct Bar;
27130            impl Bar {
27131                fn new() -> Self {
27132                    Self
27133                }
27134            }
27135            fn baz() {
27136            }
27137        "};
27138    cx.set_state(&buffer);
27139
27140    cx.update_editor(|e, _, cx| {
27141        e.buffer()
27142            .read(cx)
27143            .as_singleton()
27144            .unwrap()
27145            .update(cx, |buffer, cx| {
27146                buffer.set_language(Some(rust_lang()), cx);
27147            })
27148    });
27149
27150    let fn_foo = || empty_range(0, 0);
27151    let impl_bar = || empty_range(4, 0);
27152    let fn_new = || empty_range(5, 4);
27153
27154    let mut scroll_and_click = |scroll_offset: ScrollOffset, click_offset: ScrollOffset| {
27155        cx.update_editor(|e, window, cx| {
27156            e.scroll(
27157                gpui::Point {
27158                    x: 0.,
27159                    y: scroll_offset,
27160                },
27161                None,
27162                window,
27163                cx,
27164            );
27165        });
27166        cx.simulate_click(
27167            gpui::Point {
27168                x: px(0.),
27169                y: click_offset as f32 * line_height,
27170            },
27171            Modifiers::none(),
27172        );
27173        cx.update_editor(|e, _, cx| (e.scroll_position(cx), display_ranges(e, cx)))
27174    };
27175
27176    assert_eq!(
27177        scroll_and_click(
27178            4.5, // impl Bar is halfway off the screen
27179            0.0  // click top of screen
27180        ),
27181        // scrolled to impl Bar
27182        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
27183    );
27184
27185    assert_eq!(
27186        scroll_and_click(
27187            4.5,  // impl Bar is halfway off the screen
27188            0.25  // click middle of impl Bar
27189        ),
27190        // scrolled to impl Bar
27191        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
27192    );
27193
27194    assert_eq!(
27195        scroll_and_click(
27196            4.5, // impl Bar is halfway off the screen
27197            1.5  // click below impl Bar (e.g. fn new())
27198        ),
27199        // scrolled to fn new() - this is below the impl Bar header which has persisted
27200        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
27201    );
27202
27203    assert_eq!(
27204        scroll_and_click(
27205            5.5,  // fn new is halfway underneath impl Bar
27206            0.75  // click on the overlap of impl Bar and fn new()
27207        ),
27208        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
27209    );
27210
27211    assert_eq!(
27212        scroll_and_click(
27213            5.5,  // fn new is halfway underneath impl Bar
27214            1.25  // click on the visible part of fn new()
27215        ),
27216        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
27217    );
27218
27219    assert_eq!(
27220        scroll_and_click(
27221            1.5, // fn foo is halfway off the screen
27222            0.0  // click top of screen
27223        ),
27224        (gpui::Point { x: 0., y: 0. }, vec![fn_foo()])
27225    );
27226
27227    assert_eq!(
27228        scroll_and_click(
27229            1.5,  // fn foo is halfway off the screen
27230            0.75  // click visible part of let abc...
27231        )
27232        .0,
27233        // no change in scroll
27234        // we don't assert on the visible_range because if we clicked the gutter, our line is fully selected
27235        (gpui::Point { x: 0., y: 1.5 })
27236    );
27237}
27238
27239#[gpui::test]
27240async fn test_next_prev_reference(cx: &mut TestAppContext) {
27241    const CYCLE_POSITIONS: &[&'static str] = &[
27242        indoc! {"
27243            fn foo() {
27244                let ˇabc = 123;
27245                let x = abc + 1;
27246                let y = abc + 2;
27247                let z = abc + 2;
27248            }
27249        "},
27250        indoc! {"
27251            fn foo() {
27252                let abc = 123;
27253                let x = ˇabc + 1;
27254                let y = abc + 2;
27255                let z = abc + 2;
27256            }
27257        "},
27258        indoc! {"
27259            fn foo() {
27260                let abc = 123;
27261                let x = abc + 1;
27262                let y = ˇabc + 2;
27263                let z = abc + 2;
27264            }
27265        "},
27266        indoc! {"
27267            fn foo() {
27268                let abc = 123;
27269                let x = abc + 1;
27270                let y = abc + 2;
27271                let z = ˇabc + 2;
27272            }
27273        "},
27274    ];
27275
27276    init_test(cx, |_| {});
27277
27278    let mut cx = EditorLspTestContext::new_rust(
27279        lsp::ServerCapabilities {
27280            references_provider: Some(lsp::OneOf::Left(true)),
27281            ..Default::default()
27282        },
27283        cx,
27284    )
27285    .await;
27286
27287    // importantly, the cursor is in the middle
27288    cx.set_state(indoc! {"
27289        fn foo() {
27290            let aˇbc = 123;
27291            let x = abc + 1;
27292            let y = abc + 2;
27293            let z = abc + 2;
27294        }
27295    "});
27296
27297    let reference_ranges = [
27298        lsp::Position::new(1, 8),
27299        lsp::Position::new(2, 12),
27300        lsp::Position::new(3, 12),
27301        lsp::Position::new(4, 12),
27302    ]
27303    .map(|start| lsp::Range::new(start, lsp::Position::new(start.line, start.character + 3)));
27304
27305    cx.lsp
27306        .set_request_handler::<lsp::request::References, _, _>(move |params, _cx| async move {
27307            Ok(Some(
27308                reference_ranges
27309                    .map(|range| lsp::Location {
27310                        uri: params.text_document_position.text_document.uri.clone(),
27311                        range,
27312                    })
27313                    .to_vec(),
27314            ))
27315        });
27316
27317    let _move = async |direction, count, cx: &mut EditorLspTestContext| {
27318        cx.update_editor(|editor, window, cx| {
27319            editor.go_to_reference_before_or_after_position(direction, count, window, cx)
27320        })
27321        .unwrap()
27322        .await
27323        .unwrap()
27324    };
27325
27326    _move(Direction::Next, 1, &mut cx).await;
27327    cx.assert_editor_state(CYCLE_POSITIONS[1]);
27328
27329    _move(Direction::Next, 1, &mut cx).await;
27330    cx.assert_editor_state(CYCLE_POSITIONS[2]);
27331
27332    _move(Direction::Next, 1, &mut cx).await;
27333    cx.assert_editor_state(CYCLE_POSITIONS[3]);
27334
27335    // loops back to the start
27336    _move(Direction::Next, 1, &mut cx).await;
27337    cx.assert_editor_state(CYCLE_POSITIONS[0]);
27338
27339    // loops back to the end
27340    _move(Direction::Prev, 1, &mut cx).await;
27341    cx.assert_editor_state(CYCLE_POSITIONS[3]);
27342
27343    _move(Direction::Prev, 1, &mut cx).await;
27344    cx.assert_editor_state(CYCLE_POSITIONS[2]);
27345
27346    _move(Direction::Prev, 1, &mut cx).await;
27347    cx.assert_editor_state(CYCLE_POSITIONS[1]);
27348
27349    _move(Direction::Prev, 1, &mut cx).await;
27350    cx.assert_editor_state(CYCLE_POSITIONS[0]);
27351
27352    _move(Direction::Next, 3, &mut cx).await;
27353    cx.assert_editor_state(CYCLE_POSITIONS[3]);
27354
27355    _move(Direction::Prev, 2, &mut cx).await;
27356    cx.assert_editor_state(CYCLE_POSITIONS[1]);
27357}