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, EditorSettingsContent, IndentGuideBackgroundColoring,
   48    IndentGuideColoring, ProjectSettingsContent, SearchSettingsContent,
   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    let mut cx = EditorTestContext::new(cx).await;
 8318
 8319    // Enable case sensitive search.
 8320    update_test_editor_settings(&mut cx, |settings| {
 8321        let mut search_settings = SearchSettingsContent::default();
 8322        search_settings.case_sensitive = Some(true);
 8323        settings.search = Some(search_settings);
 8324    });
 8325
 8326    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8327
 8328    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8329        .unwrap();
 8330    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8331
 8332    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8333        .unwrap();
 8334    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8335
 8336    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8337    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8338
 8339    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8340    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8341
 8342    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8343        .unwrap();
 8344    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 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\n«abcˇ»");
 8349
 8350    // Test selection direction should be preserved
 8351    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8352
 8353    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8354        .unwrap();
 8355    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 8356
 8357    // Test case sensitivity
 8358    cx.set_state("«ˇfoo»\nFOO\nFoo\nfoo");
 8359    cx.update_editor(|e, window, cx| {
 8360        e.select_next(&SelectNext::default(), window, cx).unwrap();
 8361    });
 8362    cx.assert_editor_state("«ˇfoo»\nFOO\nFoo\n«ˇfoo»");
 8363
 8364    // Disable case sensitive search.
 8365    update_test_editor_settings(&mut cx, |settings| {
 8366        let mut search_settings = SearchSettingsContent::default();
 8367        search_settings.case_sensitive = Some(false);
 8368        settings.search = Some(search_settings);
 8369    });
 8370
 8371    cx.set_state("«ˇfoo»\nFOO\nFoo");
 8372    cx.update_editor(|e, window, cx| {
 8373        e.select_next(&SelectNext::default(), window, cx).unwrap();
 8374        e.select_next(&SelectNext::default(), window, cx).unwrap();
 8375    });
 8376    cx.assert_editor_state("«ˇfoo»\n«ˇFOO»\n«ˇFoo»");
 8377}
 8378
 8379#[gpui::test]
 8380async fn test_select_all_matches(cx: &mut TestAppContext) {
 8381    init_test(cx, |_| {});
 8382    let mut cx = EditorTestContext::new(cx).await;
 8383
 8384    // Enable case sensitive search.
 8385    update_test_editor_settings(&mut cx, |settings| {
 8386        let mut search_settings = SearchSettingsContent::default();
 8387        search_settings.case_sensitive = Some(true);
 8388        settings.search = Some(search_settings);
 8389    });
 8390
 8391    // Test caret-only selections
 8392    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8393    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8394        .unwrap();
 8395    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8396
 8397    // Test left-to-right selections
 8398    cx.set_state("abc\n«abcˇ»\nabc");
 8399    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8400        .unwrap();
 8401    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 8402
 8403    // Test right-to-left selections
 8404    cx.set_state("abc\n«ˇabc»\nabc");
 8405    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8406        .unwrap();
 8407    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 8408
 8409    // Test selecting whitespace with caret selection
 8410    cx.set_state("abc\nˇ   abc\nabc");
 8411    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8412        .unwrap();
 8413    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 8414
 8415    // Test selecting whitespace with left-to-right selection
 8416    cx.set_state("abc\n«ˇ  »abc\nabc");
 8417    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8418        .unwrap();
 8419    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 8420
 8421    // Test no matches with right-to-left selection
 8422    cx.set_state("abc\n«  ˇ»abc\nabc");
 8423    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8424        .unwrap();
 8425    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 8426
 8427    // Test with a single word and clip_at_line_ends=true (#29823)
 8428    cx.set_state("aˇbc");
 8429    cx.update_editor(|e, window, cx| {
 8430        e.set_clip_at_line_ends(true, cx);
 8431        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8432        e.set_clip_at_line_ends(false, cx);
 8433    });
 8434    cx.assert_editor_state("«abcˇ»");
 8435
 8436    // Test case sensitivity
 8437    cx.set_state("fˇoo\nFOO\nFoo");
 8438    cx.update_editor(|e, window, cx| {
 8439        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8440    });
 8441    cx.assert_editor_state("«fooˇ»\nFOO\nFoo");
 8442
 8443    // Disable case sensitive search.
 8444    update_test_editor_settings(&mut cx, |settings| {
 8445        let mut search_settings = SearchSettingsContent::default();
 8446        search_settings.case_sensitive = Some(false);
 8447        settings.search = Some(search_settings);
 8448    });
 8449
 8450    cx.set_state("fˇoo\nFOO\nFoo");
 8451    cx.update_editor(|e, window, cx| {
 8452        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8453    });
 8454    cx.assert_editor_state("«fooˇ»\n«FOOˇ»\n«Fooˇ»");
 8455}
 8456
 8457#[gpui::test]
 8458async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 8459    init_test(cx, |_| {});
 8460
 8461    let mut cx = EditorTestContext::new(cx).await;
 8462
 8463    let large_body_1 = "\nd".repeat(200);
 8464    let large_body_2 = "\ne".repeat(200);
 8465
 8466    cx.set_state(&format!(
 8467        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 8468    ));
 8469    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 8470        let scroll_position = editor.scroll_position(cx);
 8471        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 8472        scroll_position
 8473    });
 8474
 8475    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8476        .unwrap();
 8477    cx.assert_editor_state(&format!(
 8478        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 8479    ));
 8480    let scroll_position_after_selection =
 8481        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 8482    assert_eq!(
 8483        initial_scroll_position, scroll_position_after_selection,
 8484        "Scroll position should not change after selecting all matches"
 8485    );
 8486}
 8487
 8488#[gpui::test]
 8489async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 8490    init_test(cx, |_| {});
 8491
 8492    let mut cx = EditorLspTestContext::new_rust(
 8493        lsp::ServerCapabilities {
 8494            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8495            ..Default::default()
 8496        },
 8497        cx,
 8498    )
 8499    .await;
 8500
 8501    cx.set_state(indoc! {"
 8502        line 1
 8503        line 2
 8504        linˇe 3
 8505        line 4
 8506        line 5
 8507    "});
 8508
 8509    // Make an edit
 8510    cx.update_editor(|editor, window, cx| {
 8511        editor.handle_input("X", window, cx);
 8512    });
 8513
 8514    // Move cursor to a different position
 8515    cx.update_editor(|editor, window, cx| {
 8516        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8517            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 8518        });
 8519    });
 8520
 8521    cx.assert_editor_state(indoc! {"
 8522        line 1
 8523        line 2
 8524        linXe 3
 8525        line 4
 8526        liˇne 5
 8527    "});
 8528
 8529    cx.lsp
 8530        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 8531            Ok(Some(vec![lsp::TextEdit::new(
 8532                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 8533                "PREFIX ".to_string(),
 8534            )]))
 8535        });
 8536
 8537    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 8538        .unwrap()
 8539        .await
 8540        .unwrap();
 8541
 8542    cx.assert_editor_state(indoc! {"
 8543        PREFIX line 1
 8544        line 2
 8545        linXe 3
 8546        line 4
 8547        liˇne 5
 8548    "});
 8549
 8550    // Undo formatting
 8551    cx.update_editor(|editor, window, cx| {
 8552        editor.undo(&Default::default(), window, cx);
 8553    });
 8554
 8555    // Verify cursor moved back to position after edit
 8556    cx.assert_editor_state(indoc! {"
 8557        line 1
 8558        line 2
 8559        linXˇe 3
 8560        line 4
 8561        line 5
 8562    "});
 8563}
 8564
 8565#[gpui::test]
 8566async fn test_undo_edit_prediction_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 8567    init_test(cx, |_| {});
 8568
 8569    let mut cx = EditorTestContext::new(cx).await;
 8570
 8571    let provider = cx.new(|_| FakeEditPredictionProvider::default());
 8572    cx.update_editor(|editor, window, cx| {
 8573        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 8574    });
 8575
 8576    cx.set_state(indoc! {"
 8577        line 1
 8578        line 2
 8579        linˇe 3
 8580        line 4
 8581        line 5
 8582        line 6
 8583        line 7
 8584        line 8
 8585        line 9
 8586        line 10
 8587    "});
 8588
 8589    let snapshot = cx.buffer_snapshot();
 8590    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 8591
 8592    cx.update(|_, cx| {
 8593        provider.update(cx, |provider, _| {
 8594            provider.set_edit_prediction(Some(edit_prediction::EditPrediction::Local {
 8595                id: None,
 8596                edits: vec![(edit_position..edit_position, "X".into())],
 8597                edit_preview: None,
 8598            }))
 8599        })
 8600    });
 8601
 8602    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 8603    cx.update_editor(|editor, window, cx| {
 8604        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 8605    });
 8606
 8607    cx.assert_editor_state(indoc! {"
 8608        line 1
 8609        line 2
 8610        lineXˇ 3
 8611        line 4
 8612        line 5
 8613        line 6
 8614        line 7
 8615        line 8
 8616        line 9
 8617        line 10
 8618    "});
 8619
 8620    cx.update_editor(|editor, window, cx| {
 8621        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8622            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 8623        });
 8624    });
 8625
 8626    cx.assert_editor_state(indoc! {"
 8627        line 1
 8628        line 2
 8629        lineX 3
 8630        line 4
 8631        line 5
 8632        line 6
 8633        line 7
 8634        line 8
 8635        line 9
 8636        liˇne 10
 8637    "});
 8638
 8639    cx.update_editor(|editor, window, cx| {
 8640        editor.undo(&Default::default(), window, cx);
 8641    });
 8642
 8643    cx.assert_editor_state(indoc! {"
 8644        line 1
 8645        line 2
 8646        lineˇ 3
 8647        line 4
 8648        line 5
 8649        line 6
 8650        line 7
 8651        line 8
 8652        line 9
 8653        line 10
 8654    "});
 8655}
 8656
 8657#[gpui::test]
 8658async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 8659    init_test(cx, |_| {});
 8660
 8661    let mut cx = EditorTestContext::new(cx).await;
 8662    cx.set_state(
 8663        r#"let foo = 2;
 8664lˇet foo = 2;
 8665let fooˇ = 2;
 8666let foo = 2;
 8667let foo = ˇ2;"#,
 8668    );
 8669
 8670    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8671        .unwrap();
 8672    cx.assert_editor_state(
 8673        r#"let foo = 2;
 8674«letˇ» foo = 2;
 8675let «fooˇ» = 2;
 8676let foo = 2;
 8677let foo = «2ˇ»;"#,
 8678    );
 8679
 8680    // noop for multiple selections with different contents
 8681    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8682        .unwrap();
 8683    cx.assert_editor_state(
 8684        r#"let foo = 2;
 8685«letˇ» foo = 2;
 8686let «fooˇ» = 2;
 8687let foo = 2;
 8688let foo = «2ˇ»;"#,
 8689    );
 8690
 8691    // Test last selection direction should be preserved
 8692    cx.set_state(
 8693        r#"let foo = 2;
 8694let foo = 2;
 8695let «fooˇ» = 2;
 8696let «ˇfoo» = 2;
 8697let foo = 2;"#,
 8698    );
 8699
 8700    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8701        .unwrap();
 8702    cx.assert_editor_state(
 8703        r#"let foo = 2;
 8704let foo = 2;
 8705let «fooˇ» = 2;
 8706let «ˇfoo» = 2;
 8707let «ˇfoo» = 2;"#,
 8708    );
 8709}
 8710
 8711#[gpui::test]
 8712async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 8713    init_test(cx, |_| {});
 8714
 8715    let mut cx =
 8716        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 8717
 8718    cx.assert_editor_state(indoc! {"
 8719        ˇbbb
 8720        ccc
 8721
 8722        bbb
 8723        ccc
 8724        "});
 8725    cx.dispatch_action(SelectPrevious::default());
 8726    cx.assert_editor_state(indoc! {"
 8727                «bbbˇ»
 8728                ccc
 8729
 8730                bbb
 8731                ccc
 8732                "});
 8733    cx.dispatch_action(SelectPrevious::default());
 8734    cx.assert_editor_state(indoc! {"
 8735                «bbbˇ»
 8736                ccc
 8737
 8738                «bbbˇ»
 8739                ccc
 8740                "});
 8741}
 8742
 8743#[gpui::test]
 8744async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 8745    init_test(cx, |_| {});
 8746
 8747    let mut cx = EditorTestContext::new(cx).await;
 8748    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8749
 8750    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8751        .unwrap();
 8752    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8753
 8754    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8755        .unwrap();
 8756    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8757
 8758    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8759    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8760
 8761    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8762    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8763
 8764    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8765        .unwrap();
 8766    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 8767
 8768    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8769        .unwrap();
 8770    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8771}
 8772
 8773#[gpui::test]
 8774async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 8775    init_test(cx, |_| {});
 8776
 8777    let mut cx = EditorTestContext::new(cx).await;
 8778    cx.set_state("");
 8779
 8780    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8781        .unwrap();
 8782    cx.assert_editor_state("«aˇ»");
 8783    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8784        .unwrap();
 8785    cx.assert_editor_state("«aˇ»");
 8786}
 8787
 8788#[gpui::test]
 8789async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 8790    init_test(cx, |_| {});
 8791
 8792    let mut cx = EditorTestContext::new(cx).await;
 8793    cx.set_state(
 8794        r#"let foo = 2;
 8795lˇet foo = 2;
 8796let fooˇ = 2;
 8797let foo = 2;
 8798let foo = ˇ2;"#,
 8799    );
 8800
 8801    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8802        .unwrap();
 8803    cx.assert_editor_state(
 8804        r#"let foo = 2;
 8805«letˇ» foo = 2;
 8806let «fooˇ» = 2;
 8807let foo = 2;
 8808let foo = «2ˇ»;"#,
 8809    );
 8810
 8811    // noop for multiple selections with different contents
 8812    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8813        .unwrap();
 8814    cx.assert_editor_state(
 8815        r#"let foo = 2;
 8816«letˇ» foo = 2;
 8817let «fooˇ» = 2;
 8818let foo = 2;
 8819let foo = «2ˇ»;"#,
 8820    );
 8821}
 8822
 8823#[gpui::test]
 8824async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 8825    init_test(cx, |_| {});
 8826    let mut cx = EditorTestContext::new(cx).await;
 8827
 8828    // Enable case sensitive search.
 8829    update_test_editor_settings(&mut cx, |settings| {
 8830        let mut search_settings = SearchSettingsContent::default();
 8831        search_settings.case_sensitive = Some(true);
 8832        settings.search = Some(search_settings);
 8833    });
 8834
 8835    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8836
 8837    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8838        .unwrap();
 8839    // selection direction is preserved
 8840    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8841
 8842    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8843        .unwrap();
 8844    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8845
 8846    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8847    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8848
 8849    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8850    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8851
 8852    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8853        .unwrap();
 8854    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 8855
 8856    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8857        .unwrap();
 8858    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 8859
 8860    // Test case sensitivity
 8861    cx.set_state("foo\nFOO\nFoo\n«ˇfoo»");
 8862    cx.update_editor(|e, window, cx| {
 8863        e.select_previous(&SelectPrevious::default(), window, cx)
 8864            .unwrap();
 8865        e.select_previous(&SelectPrevious::default(), window, cx)
 8866            .unwrap();
 8867    });
 8868    cx.assert_editor_state("«ˇfoo»\nFOO\nFoo\n«ˇfoo»");
 8869
 8870    // Disable case sensitive search.
 8871    update_test_editor_settings(&mut cx, |settings| {
 8872        let mut search_settings = SearchSettingsContent::default();
 8873        search_settings.case_sensitive = Some(false);
 8874        settings.search = Some(search_settings);
 8875    });
 8876
 8877    cx.set_state("foo\nFOO\n«ˇFoo»");
 8878    cx.update_editor(|e, window, cx| {
 8879        e.select_previous(&SelectPrevious::default(), window, cx)
 8880            .unwrap();
 8881        e.select_previous(&SelectPrevious::default(), window, cx)
 8882            .unwrap();
 8883    });
 8884    cx.assert_editor_state("«ˇfoo»\n«ˇFOO»\n«ˇFoo»");
 8885}
 8886
 8887#[gpui::test]
 8888async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 8889    init_test(cx, |_| {});
 8890
 8891    let language = Arc::new(Language::new(
 8892        LanguageConfig::default(),
 8893        Some(tree_sitter_rust::LANGUAGE.into()),
 8894    ));
 8895
 8896    let text = r#"
 8897        use mod1::mod2::{mod3, mod4};
 8898
 8899        fn fn_1(param1: bool, param2: &str) {
 8900            let var1 = "text";
 8901        }
 8902    "#
 8903    .unindent();
 8904
 8905    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8906    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8907    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8908
 8909    editor
 8910        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8911        .await;
 8912
 8913    editor.update_in(cx, |editor, window, cx| {
 8914        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8915            s.select_display_ranges([
 8916                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 8917                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 8918                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 8919            ]);
 8920        });
 8921        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8922    });
 8923    editor.update(cx, |editor, cx| {
 8924        assert_text_with_selections(
 8925            editor,
 8926            indoc! {r#"
 8927                use mod1::mod2::{mod3, «mod4ˇ»};
 8928
 8929                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8930                    let var1 = "«ˇtext»";
 8931                }
 8932            "#},
 8933            cx,
 8934        );
 8935    });
 8936
 8937    editor.update_in(cx, |editor, window, cx| {
 8938        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8939    });
 8940    editor.update(cx, |editor, cx| {
 8941        assert_text_with_selections(
 8942            editor,
 8943            indoc! {r#"
 8944                use mod1::mod2::«{mod3, mod4}ˇ»;
 8945
 8946                «ˇfn fn_1(param1: bool, param2: &str) {
 8947                    let var1 = "text";
 8948 8949            "#},
 8950            cx,
 8951        );
 8952    });
 8953
 8954    editor.update_in(cx, |editor, window, cx| {
 8955        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8956    });
 8957    assert_eq!(
 8958        editor.update(cx, |editor, cx| editor
 8959            .selections
 8960            .display_ranges(&editor.display_snapshot(cx))),
 8961        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8962    );
 8963
 8964    // Trying to expand the selected syntax node one more time has no effect.
 8965    editor.update_in(cx, |editor, window, cx| {
 8966        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8967    });
 8968    assert_eq!(
 8969        editor.update(cx, |editor, cx| editor
 8970            .selections
 8971            .display_ranges(&editor.display_snapshot(cx))),
 8972        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8973    );
 8974
 8975    editor.update_in(cx, |editor, window, cx| {
 8976        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8977    });
 8978    editor.update(cx, |editor, cx| {
 8979        assert_text_with_selections(
 8980            editor,
 8981            indoc! {r#"
 8982                use mod1::mod2::«{mod3, mod4}ˇ»;
 8983
 8984                «ˇfn fn_1(param1: bool, param2: &str) {
 8985                    let var1 = "text";
 8986 8987            "#},
 8988            cx,
 8989        );
 8990    });
 8991
 8992    editor.update_in(cx, |editor, window, cx| {
 8993        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8994    });
 8995    editor.update(cx, |editor, cx| {
 8996        assert_text_with_selections(
 8997            editor,
 8998            indoc! {r#"
 8999                use mod1::mod2::{mod3, «mod4ˇ»};
 9000
 9001                fn fn_1«ˇ(param1: bool, param2: &str)» {
 9002                    let var1 = "«ˇtext»";
 9003                }
 9004            "#},
 9005            cx,
 9006        );
 9007    });
 9008
 9009    editor.update_in(cx, |editor, window, cx| {
 9010        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9011    });
 9012    editor.update(cx, |editor, cx| {
 9013        assert_text_with_selections(
 9014            editor,
 9015            indoc! {r#"
 9016                use mod1::mod2::{mod3, moˇd4};
 9017
 9018                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 9019                    let var1 = "teˇxt";
 9020                }
 9021            "#},
 9022            cx,
 9023        );
 9024    });
 9025
 9026    // Trying to shrink the selected syntax node one more time has no effect.
 9027    editor.update_in(cx, |editor, window, cx| {
 9028        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9029    });
 9030    editor.update_in(cx, |editor, _, cx| {
 9031        assert_text_with_selections(
 9032            editor,
 9033            indoc! {r#"
 9034                use mod1::mod2::{mod3, moˇd4};
 9035
 9036                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 9037                    let var1 = "teˇxt";
 9038                }
 9039            "#},
 9040            cx,
 9041        );
 9042    });
 9043
 9044    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 9045    // a fold.
 9046    editor.update_in(cx, |editor, window, cx| {
 9047        editor.fold_creases(
 9048            vec![
 9049                Crease::simple(
 9050                    Point::new(0, 21)..Point::new(0, 24),
 9051                    FoldPlaceholder::test(),
 9052                ),
 9053                Crease::simple(
 9054                    Point::new(3, 20)..Point::new(3, 22),
 9055                    FoldPlaceholder::test(),
 9056                ),
 9057            ],
 9058            true,
 9059            window,
 9060            cx,
 9061        );
 9062        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9063    });
 9064    editor.update(cx, |editor, cx| {
 9065        assert_text_with_selections(
 9066            editor,
 9067            indoc! {r#"
 9068                use mod1::mod2::«{mod3, mod4}ˇ»;
 9069
 9070                fn fn_1«ˇ(param1: bool, param2: &str)» {
 9071                    let var1 = "«ˇtext»";
 9072                }
 9073            "#},
 9074            cx,
 9075        );
 9076    });
 9077}
 9078
 9079#[gpui::test]
 9080async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 9081    init_test(cx, |_| {});
 9082
 9083    let language = Arc::new(Language::new(
 9084        LanguageConfig::default(),
 9085        Some(tree_sitter_rust::LANGUAGE.into()),
 9086    ));
 9087
 9088    let text = "let a = 2;";
 9089
 9090    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9091    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9092    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9093
 9094    editor
 9095        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9096        .await;
 9097
 9098    // Test case 1: Cursor at end of word
 9099    editor.update_in(cx, |editor, window, cx| {
 9100        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9101            s.select_display_ranges([
 9102                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 9103            ]);
 9104        });
 9105    });
 9106    editor.update(cx, |editor, cx| {
 9107        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 9108    });
 9109    editor.update_in(cx, |editor, window, cx| {
 9110        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9111    });
 9112    editor.update(cx, |editor, cx| {
 9113        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 9114    });
 9115    editor.update_in(cx, |editor, window, cx| {
 9116        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9117    });
 9118    editor.update(cx, |editor, cx| {
 9119        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 9120    });
 9121
 9122    // Test case 2: Cursor at end of statement
 9123    editor.update_in(cx, |editor, window, cx| {
 9124        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9125            s.select_display_ranges([
 9126                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 9127            ]);
 9128        });
 9129    });
 9130    editor.update(cx, |editor, cx| {
 9131        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 9132    });
 9133    editor.update_in(cx, |editor, window, cx| {
 9134        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9135    });
 9136    editor.update(cx, |editor, cx| {
 9137        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 9138    });
 9139}
 9140
 9141#[gpui::test]
 9142async fn test_select_larger_syntax_node_for_cursor_at_symbol(cx: &mut TestAppContext) {
 9143    init_test(cx, |_| {});
 9144
 9145    let language = Arc::new(Language::new(
 9146        LanguageConfig {
 9147            name: "JavaScript".into(),
 9148            ..Default::default()
 9149        },
 9150        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 9151    ));
 9152
 9153    let text = r#"
 9154        let a = {
 9155            key: "value",
 9156        };
 9157    "#
 9158    .unindent();
 9159
 9160    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9161    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9162    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9163
 9164    editor
 9165        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9166        .await;
 9167
 9168    // Test case 1: Cursor after '{'
 9169    editor.update_in(cx, |editor, window, cx| {
 9170        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9171            s.select_display_ranges([
 9172                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 9)
 9173            ]);
 9174        });
 9175    });
 9176    editor.update(cx, |editor, cx| {
 9177        assert_text_with_selections(
 9178            editor,
 9179            indoc! {r#"
 9180                let a = {ˇ
 9181                    key: "value",
 9182                };
 9183            "#},
 9184            cx,
 9185        );
 9186    });
 9187    editor.update_in(cx, |editor, window, cx| {
 9188        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9189    });
 9190    editor.update(cx, |editor, cx| {
 9191        assert_text_with_selections(
 9192            editor,
 9193            indoc! {r#"
 9194                let a = «ˇ{
 9195                    key: "value",
 9196                }»;
 9197            "#},
 9198            cx,
 9199        );
 9200    });
 9201
 9202    // Test case 2: Cursor after ':'
 9203    editor.update_in(cx, |editor, window, cx| {
 9204        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9205            s.select_display_ranges([
 9206                DisplayPoint::new(DisplayRow(1), 8)..DisplayPoint::new(DisplayRow(1), 8)
 9207            ]);
 9208        });
 9209    });
 9210    editor.update(cx, |editor, cx| {
 9211        assert_text_with_selections(
 9212            editor,
 9213            indoc! {r#"
 9214                let a = {
 9215                    key:ˇ "value",
 9216                };
 9217            "#},
 9218            cx,
 9219        );
 9220    });
 9221    editor.update_in(cx, |editor, window, cx| {
 9222        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9223    });
 9224    editor.update(cx, |editor, cx| {
 9225        assert_text_with_selections(
 9226            editor,
 9227            indoc! {r#"
 9228                let a = {
 9229                    «ˇkey: "value"»,
 9230                };
 9231            "#},
 9232            cx,
 9233        );
 9234    });
 9235    editor.update_in(cx, |editor, window, cx| {
 9236        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9237    });
 9238    editor.update(cx, |editor, cx| {
 9239        assert_text_with_selections(
 9240            editor,
 9241            indoc! {r#"
 9242                let a = «ˇ{
 9243                    key: "value",
 9244                }»;
 9245            "#},
 9246            cx,
 9247        );
 9248    });
 9249
 9250    // Test case 3: Cursor after ','
 9251    editor.update_in(cx, |editor, window, cx| {
 9252        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9253            s.select_display_ranges([
 9254                DisplayPoint::new(DisplayRow(1), 17)..DisplayPoint::new(DisplayRow(1), 17)
 9255            ]);
 9256        });
 9257    });
 9258    editor.update(cx, |editor, cx| {
 9259        assert_text_with_selections(
 9260            editor,
 9261            indoc! {r#"
 9262                let a = {
 9263                    key: "value",ˇ
 9264                };
 9265            "#},
 9266            cx,
 9267        );
 9268    });
 9269    editor.update_in(cx, |editor, window, cx| {
 9270        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9271    });
 9272    editor.update(cx, |editor, cx| {
 9273        assert_text_with_selections(
 9274            editor,
 9275            indoc! {r#"
 9276                let a = «ˇ{
 9277                    key: "value",
 9278                }»;
 9279            "#},
 9280            cx,
 9281        );
 9282    });
 9283
 9284    // Test case 4: Cursor after ';'
 9285    editor.update_in(cx, |editor, window, cx| {
 9286        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9287            s.select_display_ranges([
 9288                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)
 9289            ]);
 9290        });
 9291    });
 9292    editor.update(cx, |editor, cx| {
 9293        assert_text_with_selections(
 9294            editor,
 9295            indoc! {r#"
 9296                let a = {
 9297                    key: "value",
 9298                };ˇ
 9299            "#},
 9300            cx,
 9301        );
 9302    });
 9303    editor.update_in(cx, |editor, window, cx| {
 9304        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9305    });
 9306    editor.update(cx, |editor, cx| {
 9307        assert_text_with_selections(
 9308            editor,
 9309            indoc! {r#"
 9310                «ˇlet a = {
 9311                    key: "value",
 9312                };
 9313                »"#},
 9314            cx,
 9315        );
 9316    });
 9317}
 9318
 9319#[gpui::test]
 9320async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 9321    init_test(cx, |_| {});
 9322
 9323    let language = Arc::new(Language::new(
 9324        LanguageConfig::default(),
 9325        Some(tree_sitter_rust::LANGUAGE.into()),
 9326    ));
 9327
 9328    let text = r#"
 9329        use mod1::mod2::{mod3, mod4};
 9330
 9331        fn fn_1(param1: bool, param2: &str) {
 9332            let var1 = "hello world";
 9333        }
 9334    "#
 9335    .unindent();
 9336
 9337    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9338    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9339    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9340
 9341    editor
 9342        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9343        .await;
 9344
 9345    // Test 1: Cursor on a letter of a string word
 9346    editor.update_in(cx, |editor, window, cx| {
 9347        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9348            s.select_display_ranges([
 9349                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 9350            ]);
 9351        });
 9352    });
 9353    editor.update_in(cx, |editor, window, cx| {
 9354        assert_text_with_selections(
 9355            editor,
 9356            indoc! {r#"
 9357                use mod1::mod2::{mod3, mod4};
 9358
 9359                fn fn_1(param1: bool, param2: &str) {
 9360                    let var1 = "hˇello world";
 9361                }
 9362            "#},
 9363            cx,
 9364        );
 9365        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9366        assert_text_with_selections(
 9367            editor,
 9368            indoc! {r#"
 9369                use mod1::mod2::{mod3, mod4};
 9370
 9371                fn fn_1(param1: bool, param2: &str) {
 9372                    let var1 = "«ˇhello» world";
 9373                }
 9374            "#},
 9375            cx,
 9376        );
 9377    });
 9378
 9379    // Test 2: Partial selection within a word
 9380    editor.update_in(cx, |editor, window, cx| {
 9381        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9382            s.select_display_ranges([
 9383                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 9384            ]);
 9385        });
 9386    });
 9387    editor.update_in(cx, |editor, window, cx| {
 9388        assert_text_with_selections(
 9389            editor,
 9390            indoc! {r#"
 9391                use mod1::mod2::{mod3, mod4};
 9392
 9393                fn fn_1(param1: bool, param2: &str) {
 9394                    let var1 = "h«elˇ»lo world";
 9395                }
 9396            "#},
 9397            cx,
 9398        );
 9399        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9400        assert_text_with_selections(
 9401            editor,
 9402            indoc! {r#"
 9403                use mod1::mod2::{mod3, mod4};
 9404
 9405                fn fn_1(param1: bool, param2: &str) {
 9406                    let var1 = "«ˇhello» world";
 9407                }
 9408            "#},
 9409            cx,
 9410        );
 9411    });
 9412
 9413    // Test 3: Complete word already selected
 9414    editor.update_in(cx, |editor, window, cx| {
 9415        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9416            s.select_display_ranges([
 9417                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 9418            ]);
 9419        });
 9420    });
 9421    editor.update_in(cx, |editor, window, cx| {
 9422        assert_text_with_selections(
 9423            editor,
 9424            indoc! {r#"
 9425                use mod1::mod2::{mod3, mod4};
 9426
 9427                fn fn_1(param1: bool, param2: &str) {
 9428                    let var1 = "«helloˇ» world";
 9429                }
 9430            "#},
 9431            cx,
 9432        );
 9433        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9434        assert_text_with_selections(
 9435            editor,
 9436            indoc! {r#"
 9437                use mod1::mod2::{mod3, mod4};
 9438
 9439                fn fn_1(param1: bool, param2: &str) {
 9440                    let var1 = "«hello worldˇ»";
 9441                }
 9442            "#},
 9443            cx,
 9444        );
 9445    });
 9446
 9447    // Test 4: Selection spanning across words
 9448    editor.update_in(cx, |editor, window, cx| {
 9449        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9450            s.select_display_ranges([
 9451                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 9452            ]);
 9453        });
 9454    });
 9455    editor.update_in(cx, |editor, window, cx| {
 9456        assert_text_with_selections(
 9457            editor,
 9458            indoc! {r#"
 9459                use mod1::mod2::{mod3, mod4};
 9460
 9461                fn fn_1(param1: bool, param2: &str) {
 9462                    let var1 = "hel«lo woˇ»rld";
 9463                }
 9464            "#},
 9465            cx,
 9466        );
 9467        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9468        assert_text_with_selections(
 9469            editor,
 9470            indoc! {r#"
 9471                use mod1::mod2::{mod3, mod4};
 9472
 9473                fn fn_1(param1: bool, param2: &str) {
 9474                    let var1 = "«ˇhello world»";
 9475                }
 9476            "#},
 9477            cx,
 9478        );
 9479    });
 9480
 9481    // Test 5: Expansion beyond string
 9482    editor.update_in(cx, |editor, window, cx| {
 9483        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9484        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9485        assert_text_with_selections(
 9486            editor,
 9487            indoc! {r#"
 9488                use mod1::mod2::{mod3, mod4};
 9489
 9490                fn fn_1(param1: bool, param2: &str) {
 9491                    «ˇlet var1 = "hello world";»
 9492                }
 9493            "#},
 9494            cx,
 9495        );
 9496    });
 9497}
 9498
 9499#[gpui::test]
 9500async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) {
 9501    init_test(cx, |_| {});
 9502
 9503    let mut cx = EditorTestContext::new(cx).await;
 9504
 9505    let language = Arc::new(Language::new(
 9506        LanguageConfig::default(),
 9507        Some(tree_sitter_rust::LANGUAGE.into()),
 9508    ));
 9509
 9510    cx.update_buffer(|buffer, cx| {
 9511        buffer.set_language(Some(language), cx);
 9512    });
 9513
 9514    cx.set_state(indoc! { r#"use mod1::{mod2::{«mod3ˇ», mod4}, mod5::{mod6, «mod7ˇ»}};"# });
 9515    cx.update_editor(|editor, window, cx| {
 9516        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9517    });
 9518
 9519    cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# });
 9520
 9521    cx.set_state(indoc! { r#"fn a() {
 9522          // what
 9523          // a
 9524          // ˇlong
 9525          // method
 9526          // I
 9527          // sure
 9528          // hope
 9529          // it
 9530          // works
 9531    }"# });
 9532
 9533    let buffer = cx.update_multibuffer(|multibuffer, _| multibuffer.as_singleton().unwrap());
 9534    let multi_buffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 9535    cx.update(|_, cx| {
 9536        multi_buffer.update(cx, |multi_buffer, cx| {
 9537            multi_buffer.set_excerpts_for_path(
 9538                PathKey::for_buffer(&buffer, cx),
 9539                buffer,
 9540                [Point::new(1, 0)..Point::new(1, 0)],
 9541                3,
 9542                cx,
 9543            );
 9544        });
 9545    });
 9546
 9547    let editor2 = cx.new_window_entity(|window, cx| {
 9548        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
 9549    });
 9550
 9551    let mut cx = EditorTestContext::for_editor_in(editor2, &mut cx).await;
 9552    cx.update_editor(|editor, window, cx| {
 9553        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 9554            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)]);
 9555        })
 9556    });
 9557
 9558    cx.assert_editor_state(indoc! { "
 9559        fn a() {
 9560              // what
 9561              // a
 9562        ˇ      // long
 9563              // method"});
 9564
 9565    cx.update_editor(|editor, window, cx| {
 9566        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9567    });
 9568
 9569    // Although we could potentially make the action work when the syntax node
 9570    // is half-hidden, it seems a bit dangerous as you can't easily tell what it
 9571    // did. Maybe we could also expand the excerpt to contain the range?
 9572    cx.assert_editor_state(indoc! { "
 9573        fn a() {
 9574              // what
 9575              // a
 9576        ˇ      // long
 9577              // method"});
 9578}
 9579
 9580#[gpui::test]
 9581async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 9582    init_test(cx, |_| {});
 9583
 9584    let base_text = r#"
 9585        impl A {
 9586            // this is an uncommitted comment
 9587
 9588            fn b() {
 9589                c();
 9590            }
 9591
 9592            // this is another uncommitted comment
 9593
 9594            fn d() {
 9595                // e
 9596                // f
 9597            }
 9598        }
 9599
 9600        fn g() {
 9601            // h
 9602        }
 9603    "#
 9604    .unindent();
 9605
 9606    let text = r#"
 9607        ˇimpl A {
 9608
 9609            fn b() {
 9610                c();
 9611            }
 9612
 9613            fn d() {
 9614                // e
 9615                // f
 9616            }
 9617        }
 9618
 9619        fn g() {
 9620            // h
 9621        }
 9622    "#
 9623    .unindent();
 9624
 9625    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9626    cx.set_state(&text);
 9627    cx.set_head_text(&base_text);
 9628    cx.update_editor(|editor, window, cx| {
 9629        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 9630    });
 9631
 9632    cx.assert_state_with_diff(
 9633        "
 9634        ˇimpl A {
 9635      -     // this is an uncommitted comment
 9636
 9637            fn b() {
 9638                c();
 9639            }
 9640
 9641      -     // this is another uncommitted comment
 9642      -
 9643            fn d() {
 9644                // e
 9645                // f
 9646            }
 9647        }
 9648
 9649        fn g() {
 9650            // h
 9651        }
 9652    "
 9653        .unindent(),
 9654    );
 9655
 9656    let expected_display_text = "
 9657        impl A {
 9658            // this is an uncommitted comment
 9659
 9660            fn b() {
 9661 9662            }
 9663
 9664            // this is another uncommitted comment
 9665
 9666            fn d() {
 9667 9668            }
 9669        }
 9670
 9671        fn g() {
 9672 9673        }
 9674        "
 9675    .unindent();
 9676
 9677    cx.update_editor(|editor, window, cx| {
 9678        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 9679        assert_eq!(editor.display_text(cx), expected_display_text);
 9680    });
 9681}
 9682
 9683#[gpui::test]
 9684async fn test_autoindent(cx: &mut TestAppContext) {
 9685    init_test(cx, |_| {});
 9686
 9687    let language = Arc::new(
 9688        Language::new(
 9689            LanguageConfig {
 9690                brackets: BracketPairConfig {
 9691                    pairs: vec![
 9692                        BracketPair {
 9693                            start: "{".to_string(),
 9694                            end: "}".to_string(),
 9695                            close: false,
 9696                            surround: false,
 9697                            newline: true,
 9698                        },
 9699                        BracketPair {
 9700                            start: "(".to_string(),
 9701                            end: ")".to_string(),
 9702                            close: false,
 9703                            surround: false,
 9704                            newline: true,
 9705                        },
 9706                    ],
 9707                    ..Default::default()
 9708                },
 9709                ..Default::default()
 9710            },
 9711            Some(tree_sitter_rust::LANGUAGE.into()),
 9712        )
 9713        .with_indents_query(
 9714            r#"
 9715                (_ "(" ")" @end) @indent
 9716                (_ "{" "}" @end) @indent
 9717            "#,
 9718        )
 9719        .unwrap(),
 9720    );
 9721
 9722    let text = "fn a() {}";
 9723
 9724    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9725    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9726    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9727    editor
 9728        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9729        .await;
 9730
 9731    editor.update_in(cx, |editor, window, cx| {
 9732        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9733            s.select_ranges([5..5, 8..8, 9..9])
 9734        });
 9735        editor.newline(&Newline, window, cx);
 9736        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 9737        assert_eq!(
 9738            editor.selections.ranges(&editor.display_snapshot(cx)),
 9739            &[
 9740                Point::new(1, 4)..Point::new(1, 4),
 9741                Point::new(3, 4)..Point::new(3, 4),
 9742                Point::new(5, 0)..Point::new(5, 0)
 9743            ]
 9744        );
 9745    });
 9746}
 9747
 9748#[gpui::test]
 9749async fn test_autoindent_disabled(cx: &mut TestAppContext) {
 9750    init_test(cx, |settings| settings.defaults.auto_indent = Some(false));
 9751
 9752    let 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: false,
 9768                            surround: false,
 9769                            newline: true,
 9770                        },
 9771                    ],
 9772                    ..Default::default()
 9773                },
 9774                ..Default::default()
 9775            },
 9776            Some(tree_sitter_rust::LANGUAGE.into()),
 9777        )
 9778        .with_indents_query(
 9779            r#"
 9780                (_ "(" ")" @end) @indent
 9781                (_ "{" "}" @end) @indent
 9782            "#,
 9783        )
 9784        .unwrap(),
 9785    );
 9786
 9787    let text = "fn a() {}";
 9788
 9789    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9790    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9791    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9792    editor
 9793        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9794        .await;
 9795
 9796    editor.update_in(cx, |editor, window, cx| {
 9797        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9798            s.select_ranges([5..5, 8..8, 9..9])
 9799        });
 9800        editor.newline(&Newline, window, cx);
 9801        assert_eq!(
 9802            editor.text(cx),
 9803            indoc!(
 9804                "
 9805                fn a(
 9806
 9807                ) {
 9808
 9809                }
 9810                "
 9811            )
 9812        );
 9813        assert_eq!(
 9814            editor.selections.ranges(&editor.display_snapshot(cx)),
 9815            &[
 9816                Point::new(1, 0)..Point::new(1, 0),
 9817                Point::new(3, 0)..Point::new(3, 0),
 9818                Point::new(5, 0)..Point::new(5, 0)
 9819            ]
 9820        );
 9821    });
 9822}
 9823
 9824#[gpui::test]
 9825async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
 9826    init_test(cx, |settings| {
 9827        settings.defaults.auto_indent = Some(true);
 9828        settings.languages.0.insert(
 9829            "python".into(),
 9830            LanguageSettingsContent {
 9831                auto_indent: Some(false),
 9832                ..Default::default()
 9833            },
 9834        );
 9835    });
 9836
 9837    let mut cx = EditorTestContext::new(cx).await;
 9838
 9839    let injected_language = Arc::new(
 9840        Language::new(
 9841            LanguageConfig {
 9842                brackets: BracketPairConfig {
 9843                    pairs: vec![
 9844                        BracketPair {
 9845                            start: "{".to_string(),
 9846                            end: "}".to_string(),
 9847                            close: false,
 9848                            surround: false,
 9849                            newline: true,
 9850                        },
 9851                        BracketPair {
 9852                            start: "(".to_string(),
 9853                            end: ")".to_string(),
 9854                            close: true,
 9855                            surround: false,
 9856                            newline: true,
 9857                        },
 9858                    ],
 9859                    ..Default::default()
 9860                },
 9861                name: "python".into(),
 9862                ..Default::default()
 9863            },
 9864            Some(tree_sitter_python::LANGUAGE.into()),
 9865        )
 9866        .with_indents_query(
 9867            r#"
 9868                (_ "(" ")" @end) @indent
 9869                (_ "{" "}" @end) @indent
 9870            "#,
 9871        )
 9872        .unwrap(),
 9873    );
 9874
 9875    let language = Arc::new(
 9876        Language::new(
 9877            LanguageConfig {
 9878                brackets: BracketPairConfig {
 9879                    pairs: vec![
 9880                        BracketPair {
 9881                            start: "{".to_string(),
 9882                            end: "}".to_string(),
 9883                            close: false,
 9884                            surround: false,
 9885                            newline: true,
 9886                        },
 9887                        BracketPair {
 9888                            start: "(".to_string(),
 9889                            end: ")".to_string(),
 9890                            close: true,
 9891                            surround: false,
 9892                            newline: true,
 9893                        },
 9894                    ],
 9895                    ..Default::default()
 9896                },
 9897                name: LanguageName::new("rust"),
 9898                ..Default::default()
 9899            },
 9900            Some(tree_sitter_rust::LANGUAGE.into()),
 9901        )
 9902        .with_indents_query(
 9903            r#"
 9904                (_ "(" ")" @end) @indent
 9905                (_ "{" "}" @end) @indent
 9906            "#,
 9907        )
 9908        .unwrap()
 9909        .with_injection_query(
 9910            r#"
 9911            (macro_invocation
 9912                macro: (identifier) @_macro_name
 9913                (token_tree) @injection.content
 9914                (#set! injection.language "python"))
 9915           "#,
 9916        )
 9917        .unwrap(),
 9918    );
 9919
 9920    cx.language_registry().add(injected_language);
 9921    cx.language_registry().add(language.clone());
 9922
 9923    cx.update_buffer(|buffer, cx| {
 9924        buffer.set_language(Some(language), cx);
 9925    });
 9926
 9927    cx.set_state(r#"struct A {ˇ}"#);
 9928
 9929    cx.update_editor(|editor, window, cx| {
 9930        editor.newline(&Default::default(), window, cx);
 9931    });
 9932
 9933    cx.assert_editor_state(indoc!(
 9934        "struct A {
 9935            ˇ
 9936        }"
 9937    ));
 9938
 9939    cx.set_state(r#"select_biased!(ˇ)"#);
 9940
 9941    cx.update_editor(|editor, window, cx| {
 9942        editor.newline(&Default::default(), window, cx);
 9943        editor.handle_input("def ", window, cx);
 9944        editor.handle_input("(", window, cx);
 9945        editor.newline(&Default::default(), window, cx);
 9946        editor.handle_input("a", window, cx);
 9947    });
 9948
 9949    cx.assert_editor_state(indoc!(
 9950        "select_biased!(
 9951        def (
 9952 9953        )
 9954        )"
 9955    ));
 9956}
 9957
 9958#[gpui::test]
 9959async fn test_autoindent_selections(cx: &mut TestAppContext) {
 9960    init_test(cx, |_| {});
 9961
 9962    {
 9963        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9964        cx.set_state(indoc! {"
 9965            impl A {
 9966
 9967                fn b() {}
 9968
 9969            «fn c() {
 9970
 9971            }ˇ»
 9972            }
 9973        "});
 9974
 9975        cx.update_editor(|editor, window, cx| {
 9976            editor.autoindent(&Default::default(), window, cx);
 9977        });
 9978
 9979        cx.assert_editor_state(indoc! {"
 9980            impl A {
 9981
 9982                fn b() {}
 9983
 9984                «fn c() {
 9985
 9986                }ˇ»
 9987            }
 9988        "});
 9989    }
 9990
 9991    {
 9992        let mut cx = EditorTestContext::new_multibuffer(
 9993            cx,
 9994            [indoc! { "
 9995                impl A {
 9996                «
 9997                // a
 9998                fn b(){}
 9999                »
10000                «
10001                    }
10002                    fn c(){}
10003                »
10004            "}],
10005        );
10006
10007        let buffer = cx.update_editor(|editor, _, cx| {
10008            let buffer = editor.buffer().update(cx, |buffer, _| {
10009                buffer.all_buffers().iter().next().unwrap().clone()
10010            });
10011            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
10012            buffer
10013        });
10014
10015        cx.run_until_parked();
10016        cx.update_editor(|editor, window, cx| {
10017            editor.select_all(&Default::default(), window, cx);
10018            editor.autoindent(&Default::default(), window, cx)
10019        });
10020        cx.run_until_parked();
10021
10022        cx.update(|_, cx| {
10023            assert_eq!(
10024                buffer.read(cx).text(),
10025                indoc! { "
10026                    impl A {
10027
10028                        // a
10029                        fn b(){}
10030
10031
10032                    }
10033                    fn c(){}
10034
10035                " }
10036            )
10037        });
10038    }
10039}
10040
10041#[gpui::test]
10042async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
10043    init_test(cx, |_| {});
10044
10045    let mut cx = EditorTestContext::new(cx).await;
10046
10047    let language = Arc::new(Language::new(
10048        LanguageConfig {
10049            brackets: BracketPairConfig {
10050                pairs: vec![
10051                    BracketPair {
10052                        start: "{".to_string(),
10053                        end: "}".to_string(),
10054                        close: true,
10055                        surround: true,
10056                        newline: true,
10057                    },
10058                    BracketPair {
10059                        start: "(".to_string(),
10060                        end: ")".to_string(),
10061                        close: true,
10062                        surround: true,
10063                        newline: true,
10064                    },
10065                    BracketPair {
10066                        start: "/*".to_string(),
10067                        end: " */".to_string(),
10068                        close: true,
10069                        surround: true,
10070                        newline: true,
10071                    },
10072                    BracketPair {
10073                        start: "[".to_string(),
10074                        end: "]".to_string(),
10075                        close: false,
10076                        surround: false,
10077                        newline: true,
10078                    },
10079                    BracketPair {
10080                        start: "\"".to_string(),
10081                        end: "\"".to_string(),
10082                        close: true,
10083                        surround: true,
10084                        newline: false,
10085                    },
10086                    BracketPair {
10087                        start: "<".to_string(),
10088                        end: ">".to_string(),
10089                        close: false,
10090                        surround: true,
10091                        newline: true,
10092                    },
10093                ],
10094                ..Default::default()
10095            },
10096            autoclose_before: "})]".to_string(),
10097            ..Default::default()
10098        },
10099        Some(tree_sitter_rust::LANGUAGE.into()),
10100    ));
10101
10102    cx.language_registry().add(language.clone());
10103    cx.update_buffer(|buffer, cx| {
10104        buffer.set_language(Some(language), cx);
10105    });
10106
10107    cx.set_state(
10108        &r#"
10109            🏀ˇ
10110            εˇ
10111            ❤️ˇ
10112        "#
10113        .unindent(),
10114    );
10115
10116    // autoclose multiple nested brackets at multiple cursors
10117    cx.update_editor(|editor, window, cx| {
10118        editor.handle_input("{", window, cx);
10119        editor.handle_input("{", window, cx);
10120        editor.handle_input("{", window, cx);
10121    });
10122    cx.assert_editor_state(
10123        &"
10124            🏀{{{ˇ}}}
10125            ε{{{ˇ}}}
10126            ❤️{{{ˇ}}}
10127        "
10128        .unindent(),
10129    );
10130
10131    // insert a different closing bracket
10132    cx.update_editor(|editor, window, cx| {
10133        editor.handle_input(")", window, cx);
10134    });
10135    cx.assert_editor_state(
10136        &"
10137            🏀{{{)ˇ}}}
10138            ε{{{)ˇ}}}
10139            ❤️{{{)ˇ}}}
10140        "
10141        .unindent(),
10142    );
10143
10144    // skip over the auto-closed brackets when typing a closing bracket
10145    cx.update_editor(|editor, window, cx| {
10146        editor.move_right(&MoveRight, window, cx);
10147        editor.handle_input("}", window, cx);
10148        editor.handle_input("}", window, cx);
10149        editor.handle_input("}", window, cx);
10150    });
10151    cx.assert_editor_state(
10152        &"
10153            🏀{{{)}}}}ˇ
10154            ε{{{)}}}}ˇ
10155            ❤️{{{)}}}}ˇ
10156        "
10157        .unindent(),
10158    );
10159
10160    // autoclose multi-character pairs
10161    cx.set_state(
10162        &"
10163            ˇ
10164            ˇ
10165        "
10166        .unindent(),
10167    );
10168    cx.update_editor(|editor, window, cx| {
10169        editor.handle_input("/", window, cx);
10170        editor.handle_input("*", window, cx);
10171    });
10172    cx.assert_editor_state(
10173        &"
10174            /*ˇ */
10175            /*ˇ */
10176        "
10177        .unindent(),
10178    );
10179
10180    // one cursor autocloses a multi-character pair, one cursor
10181    // does not autoclose.
10182    cx.set_state(
10183        &"
1018410185            ˇ
10186        "
10187        .unindent(),
10188    );
10189    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
10190    cx.assert_editor_state(
10191        &"
10192            /*ˇ */
1019310194        "
10195        .unindent(),
10196    );
10197
10198    // Don't autoclose if the next character isn't whitespace and isn't
10199    // listed in the language's "autoclose_before" section.
10200    cx.set_state("ˇa b");
10201    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10202    cx.assert_editor_state("{ˇa b");
10203
10204    // Don't autoclose if `close` is false for the bracket pair
10205    cx.set_state("ˇ");
10206    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
10207    cx.assert_editor_state("");
10208
10209    // Surround with brackets if text is selected
10210    cx.set_state("«aˇ» b");
10211    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10212    cx.assert_editor_state("{«aˇ»} b");
10213
10214    // Autoclose when not immediately after a word character
10215    cx.set_state("a ˇ");
10216    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10217    cx.assert_editor_state("a \"ˇ\"");
10218
10219    // Autoclose pair where the start and end characters are the same
10220    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10221    cx.assert_editor_state("a \"\"ˇ");
10222
10223    // Don't autoclose when immediately after a word character
10224    cx.set_state("");
10225    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10226    cx.assert_editor_state("a\"ˇ");
10227
10228    // Do autoclose when after a non-word character
10229    cx.set_state("");
10230    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10231    cx.assert_editor_state("{\"ˇ\"");
10232
10233    // Non identical pairs autoclose regardless of preceding character
10234    cx.set_state("");
10235    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10236    cx.assert_editor_state("a{ˇ}");
10237
10238    // Don't autoclose pair if autoclose is disabled
10239    cx.set_state("ˇ");
10240    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
10241    cx.assert_editor_state("");
10242
10243    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
10244    cx.set_state("«aˇ» b");
10245    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
10246    cx.assert_editor_state("<«aˇ»> b");
10247}
10248
10249#[gpui::test]
10250async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
10251    init_test(cx, |settings| {
10252        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10253    });
10254
10255    let mut cx = EditorTestContext::new(cx).await;
10256
10257    let language = Arc::new(Language::new(
10258        LanguageConfig {
10259            brackets: BracketPairConfig {
10260                pairs: vec![
10261                    BracketPair {
10262                        start: "{".to_string(),
10263                        end: "}".to_string(),
10264                        close: true,
10265                        surround: true,
10266                        newline: true,
10267                    },
10268                    BracketPair {
10269                        start: "(".to_string(),
10270                        end: ")".to_string(),
10271                        close: true,
10272                        surround: true,
10273                        newline: true,
10274                    },
10275                    BracketPair {
10276                        start: "[".to_string(),
10277                        end: "]".to_string(),
10278                        close: false,
10279                        surround: false,
10280                        newline: true,
10281                    },
10282                ],
10283                ..Default::default()
10284            },
10285            autoclose_before: "})]".to_string(),
10286            ..Default::default()
10287        },
10288        Some(tree_sitter_rust::LANGUAGE.into()),
10289    ));
10290
10291    cx.language_registry().add(language.clone());
10292    cx.update_buffer(|buffer, cx| {
10293        buffer.set_language(Some(language), cx);
10294    });
10295
10296    cx.set_state(
10297        &"
10298            ˇ
10299            ˇ
10300            ˇ
10301        "
10302        .unindent(),
10303    );
10304
10305    // ensure only matching closing brackets are skipped over
10306    cx.update_editor(|editor, window, cx| {
10307        editor.handle_input("}", window, cx);
10308        editor.move_left(&MoveLeft, window, cx);
10309        editor.handle_input(")", window, cx);
10310        editor.move_left(&MoveLeft, window, cx);
10311    });
10312    cx.assert_editor_state(
10313        &"
10314            ˇ)}
10315            ˇ)}
10316            ˇ)}
10317        "
10318        .unindent(),
10319    );
10320
10321    // skip-over closing brackets at multiple cursors
10322    cx.update_editor(|editor, window, cx| {
10323        editor.handle_input(")", window, cx);
10324        editor.handle_input("}", window, cx);
10325    });
10326    cx.assert_editor_state(
10327        &"
10328            )}ˇ
10329            )}ˇ
10330            )}ˇ
10331        "
10332        .unindent(),
10333    );
10334
10335    // ignore non-close brackets
10336    cx.update_editor(|editor, window, cx| {
10337        editor.handle_input("]", window, cx);
10338        editor.move_left(&MoveLeft, window, cx);
10339        editor.handle_input("]", window, cx);
10340    });
10341    cx.assert_editor_state(
10342        &"
10343            )}]ˇ]
10344            )}]ˇ]
10345            )}]ˇ]
10346        "
10347        .unindent(),
10348    );
10349}
10350
10351#[gpui::test]
10352async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
10353    init_test(cx, |_| {});
10354
10355    let mut cx = EditorTestContext::new(cx).await;
10356
10357    let html_language = Arc::new(
10358        Language::new(
10359            LanguageConfig {
10360                name: "HTML".into(),
10361                brackets: BracketPairConfig {
10362                    pairs: vec![
10363                        BracketPair {
10364                            start: "<".into(),
10365                            end: ">".into(),
10366                            close: true,
10367                            ..Default::default()
10368                        },
10369                        BracketPair {
10370                            start: "{".into(),
10371                            end: "}".into(),
10372                            close: true,
10373                            ..Default::default()
10374                        },
10375                        BracketPair {
10376                            start: "(".into(),
10377                            end: ")".into(),
10378                            close: true,
10379                            ..Default::default()
10380                        },
10381                    ],
10382                    ..Default::default()
10383                },
10384                autoclose_before: "})]>".into(),
10385                ..Default::default()
10386            },
10387            Some(tree_sitter_html::LANGUAGE.into()),
10388        )
10389        .with_injection_query(
10390            r#"
10391            (script_element
10392                (raw_text) @injection.content
10393                (#set! injection.language "javascript"))
10394            "#,
10395        )
10396        .unwrap(),
10397    );
10398
10399    let javascript_language = Arc::new(Language::new(
10400        LanguageConfig {
10401            name: "JavaScript".into(),
10402            brackets: BracketPairConfig {
10403                pairs: vec![
10404                    BracketPair {
10405                        start: "/*".into(),
10406                        end: " */".into(),
10407                        close: true,
10408                        ..Default::default()
10409                    },
10410                    BracketPair {
10411                        start: "{".into(),
10412                        end: "}".into(),
10413                        close: true,
10414                        ..Default::default()
10415                    },
10416                    BracketPair {
10417                        start: "(".into(),
10418                        end: ")".into(),
10419                        close: true,
10420                        ..Default::default()
10421                    },
10422                ],
10423                ..Default::default()
10424            },
10425            autoclose_before: "})]>".into(),
10426            ..Default::default()
10427        },
10428        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10429    ));
10430
10431    cx.language_registry().add(html_language.clone());
10432    cx.language_registry().add(javascript_language);
10433    cx.executor().run_until_parked();
10434
10435    cx.update_buffer(|buffer, cx| {
10436        buffer.set_language(Some(html_language), cx);
10437    });
10438
10439    cx.set_state(
10440        &r#"
10441            <body>ˇ
10442                <script>
10443                    var x = 1;ˇ
10444                </script>
10445            </body>ˇ
10446        "#
10447        .unindent(),
10448    );
10449
10450    // Precondition: different languages are active at different locations.
10451    cx.update_editor(|editor, window, cx| {
10452        let snapshot = editor.snapshot(window, cx);
10453        let cursors = editor
10454            .selections
10455            .ranges::<usize>(&editor.display_snapshot(cx));
10456        let languages = cursors
10457            .iter()
10458            .map(|c| snapshot.language_at(c.start).unwrap().name())
10459            .collect::<Vec<_>>();
10460        assert_eq!(
10461            languages,
10462            &["HTML".into(), "JavaScript".into(), "HTML".into()]
10463        );
10464    });
10465
10466    // Angle brackets autoclose in HTML, but not JavaScript.
10467    cx.update_editor(|editor, window, cx| {
10468        editor.handle_input("<", window, cx);
10469        editor.handle_input("a", window, cx);
10470    });
10471    cx.assert_editor_state(
10472        &r#"
10473            <body><aˇ>
10474                <script>
10475                    var x = 1;<aˇ
10476                </script>
10477            </body><aˇ>
10478        "#
10479        .unindent(),
10480    );
10481
10482    // Curly braces and parens autoclose in both HTML and JavaScript.
10483    cx.update_editor(|editor, window, cx| {
10484        editor.handle_input(" b=", window, cx);
10485        editor.handle_input("{", window, cx);
10486        editor.handle_input("c", window, cx);
10487        editor.handle_input("(", window, cx);
10488    });
10489    cx.assert_editor_state(
10490        &r#"
10491            <body><a b={c(ˇ)}>
10492                <script>
10493                    var x = 1;<a b={c(ˇ)}
10494                </script>
10495            </body><a b={c(ˇ)}>
10496        "#
10497        .unindent(),
10498    );
10499
10500    // Brackets that were already autoclosed are skipped.
10501    cx.update_editor(|editor, window, cx| {
10502        editor.handle_input(")", window, cx);
10503        editor.handle_input("d", window, cx);
10504        editor.handle_input("}", window, cx);
10505    });
10506    cx.assert_editor_state(
10507        &r#"
10508            <body><a b={c()d}ˇ>
10509                <script>
10510                    var x = 1;<a b={c()d}ˇ
10511                </script>
10512            </body><a b={c()d}ˇ>
10513        "#
10514        .unindent(),
10515    );
10516    cx.update_editor(|editor, window, cx| {
10517        editor.handle_input(">", window, cx);
10518    });
10519    cx.assert_editor_state(
10520        &r#"
10521            <body><a b={c()d}>ˇ
10522                <script>
10523                    var x = 1;<a b={c()d}>ˇ
10524                </script>
10525            </body><a b={c()d}>ˇ
10526        "#
10527        .unindent(),
10528    );
10529
10530    // Reset
10531    cx.set_state(
10532        &r#"
10533            <body>ˇ
10534                <script>
10535                    var x = 1;ˇ
10536                </script>
10537            </body>ˇ
10538        "#
10539        .unindent(),
10540    );
10541
10542    cx.update_editor(|editor, window, cx| {
10543        editor.handle_input("<", window, cx);
10544    });
10545    cx.assert_editor_state(
10546        &r#"
10547            <body><ˇ>
10548                <script>
10549                    var x = 1;<ˇ
10550                </script>
10551            </body><ˇ>
10552        "#
10553        .unindent(),
10554    );
10555
10556    // When backspacing, the closing angle brackets are removed.
10557    cx.update_editor(|editor, window, cx| {
10558        editor.backspace(&Backspace, window, cx);
10559    });
10560    cx.assert_editor_state(
10561        &r#"
10562            <body>ˇ
10563                <script>
10564                    var x = 1;ˇ
10565                </script>
10566            </body>ˇ
10567        "#
10568        .unindent(),
10569    );
10570
10571    // Block comments autoclose in JavaScript, but not HTML.
10572    cx.update_editor(|editor, window, cx| {
10573        editor.handle_input("/", window, cx);
10574        editor.handle_input("*", window, cx);
10575    });
10576    cx.assert_editor_state(
10577        &r#"
10578            <body>/*ˇ
10579                <script>
10580                    var x = 1;/*ˇ */
10581                </script>
10582            </body>/*ˇ
10583        "#
10584        .unindent(),
10585    );
10586}
10587
10588#[gpui::test]
10589async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
10590    init_test(cx, |_| {});
10591
10592    let mut cx = EditorTestContext::new(cx).await;
10593
10594    let rust_language = Arc::new(
10595        Language::new(
10596            LanguageConfig {
10597                name: "Rust".into(),
10598                brackets: serde_json::from_value(json!([
10599                    { "start": "{", "end": "}", "close": true, "newline": true },
10600                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
10601                ]))
10602                .unwrap(),
10603                autoclose_before: "})]>".into(),
10604                ..Default::default()
10605            },
10606            Some(tree_sitter_rust::LANGUAGE.into()),
10607        )
10608        .with_override_query("(string_literal) @string")
10609        .unwrap(),
10610    );
10611
10612    cx.language_registry().add(rust_language.clone());
10613    cx.update_buffer(|buffer, cx| {
10614        buffer.set_language(Some(rust_language), cx);
10615    });
10616
10617    cx.set_state(
10618        &r#"
10619            let x = ˇ
10620        "#
10621        .unindent(),
10622    );
10623
10624    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
10625    cx.update_editor(|editor, window, cx| {
10626        editor.handle_input("\"", window, cx);
10627    });
10628    cx.assert_editor_state(
10629        &r#"
10630            let x = "ˇ"
10631        "#
10632        .unindent(),
10633    );
10634
10635    // Inserting another quotation mark. The cursor moves across the existing
10636    // automatically-inserted quotation mark.
10637    cx.update_editor(|editor, window, cx| {
10638        editor.handle_input("\"", window, cx);
10639    });
10640    cx.assert_editor_state(
10641        &r#"
10642            let x = ""ˇ
10643        "#
10644        .unindent(),
10645    );
10646
10647    // Reset
10648    cx.set_state(
10649        &r#"
10650            let x = ˇ
10651        "#
10652        .unindent(),
10653    );
10654
10655    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
10656    cx.update_editor(|editor, window, cx| {
10657        editor.handle_input("\"", window, cx);
10658        editor.handle_input(" ", window, cx);
10659        editor.move_left(&Default::default(), window, cx);
10660        editor.handle_input("\\", window, cx);
10661        editor.handle_input("\"", window, cx);
10662    });
10663    cx.assert_editor_state(
10664        &r#"
10665            let x = "\"ˇ "
10666        "#
10667        .unindent(),
10668    );
10669
10670    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
10671    // mark. Nothing is inserted.
10672    cx.update_editor(|editor, window, cx| {
10673        editor.move_right(&Default::default(), window, cx);
10674        editor.handle_input("\"", window, cx);
10675    });
10676    cx.assert_editor_state(
10677        &r#"
10678            let x = "\" "ˇ
10679        "#
10680        .unindent(),
10681    );
10682}
10683
10684#[gpui::test]
10685async fn test_surround_with_pair(cx: &mut TestAppContext) {
10686    init_test(cx, |_| {});
10687
10688    let language = Arc::new(Language::new(
10689        LanguageConfig {
10690            brackets: BracketPairConfig {
10691                pairs: vec![
10692                    BracketPair {
10693                        start: "{".to_string(),
10694                        end: "}".to_string(),
10695                        close: true,
10696                        surround: true,
10697                        newline: true,
10698                    },
10699                    BracketPair {
10700                        start: "/* ".to_string(),
10701                        end: "*/".to_string(),
10702                        close: true,
10703                        surround: true,
10704                        ..Default::default()
10705                    },
10706                ],
10707                ..Default::default()
10708            },
10709            ..Default::default()
10710        },
10711        Some(tree_sitter_rust::LANGUAGE.into()),
10712    ));
10713
10714    let text = r#"
10715        a
10716        b
10717        c
10718    "#
10719    .unindent();
10720
10721    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10722    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10723    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10724    editor
10725        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10726        .await;
10727
10728    editor.update_in(cx, |editor, window, cx| {
10729        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10730            s.select_display_ranges([
10731                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10732                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10733                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
10734            ])
10735        });
10736
10737        editor.handle_input("{", window, cx);
10738        editor.handle_input("{", window, cx);
10739        editor.handle_input("{", window, cx);
10740        assert_eq!(
10741            editor.text(cx),
10742            "
10743                {{{a}}}
10744                {{{b}}}
10745                {{{c}}}
10746            "
10747            .unindent()
10748        );
10749        assert_eq!(
10750            display_ranges(editor, cx),
10751            [
10752                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
10753                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
10754                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
10755            ]
10756        );
10757
10758        editor.undo(&Undo, window, cx);
10759        editor.undo(&Undo, window, cx);
10760        editor.undo(&Undo, window, cx);
10761        assert_eq!(
10762            editor.text(cx),
10763            "
10764                a
10765                b
10766                c
10767            "
10768            .unindent()
10769        );
10770        assert_eq!(
10771            display_ranges(editor, cx),
10772            [
10773                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10774                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10775                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10776            ]
10777        );
10778
10779        // Ensure inserting the first character of a multi-byte bracket pair
10780        // doesn't surround the selections with the bracket.
10781        editor.handle_input("/", window, cx);
10782        assert_eq!(
10783            editor.text(cx),
10784            "
10785                /
10786                /
10787                /
10788            "
10789            .unindent()
10790        );
10791        assert_eq!(
10792            display_ranges(editor, cx),
10793            [
10794                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10795                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10796                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10797            ]
10798        );
10799
10800        editor.undo(&Undo, window, cx);
10801        assert_eq!(
10802            editor.text(cx),
10803            "
10804                a
10805                b
10806                c
10807            "
10808            .unindent()
10809        );
10810        assert_eq!(
10811            display_ranges(editor, cx),
10812            [
10813                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10814                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10815                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10816            ]
10817        );
10818
10819        // Ensure inserting the last character of a multi-byte bracket pair
10820        // doesn't surround the selections with the bracket.
10821        editor.handle_input("*", window, cx);
10822        assert_eq!(
10823            editor.text(cx),
10824            "
10825                *
10826                *
10827                *
10828            "
10829            .unindent()
10830        );
10831        assert_eq!(
10832            display_ranges(editor, cx),
10833            [
10834                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10835                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10836                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10837            ]
10838        );
10839    });
10840}
10841
10842#[gpui::test]
10843async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
10844    init_test(cx, |_| {});
10845
10846    let language = Arc::new(Language::new(
10847        LanguageConfig {
10848            brackets: BracketPairConfig {
10849                pairs: vec![BracketPair {
10850                    start: "{".to_string(),
10851                    end: "}".to_string(),
10852                    close: true,
10853                    surround: true,
10854                    newline: true,
10855                }],
10856                ..Default::default()
10857            },
10858            autoclose_before: "}".to_string(),
10859            ..Default::default()
10860        },
10861        Some(tree_sitter_rust::LANGUAGE.into()),
10862    ));
10863
10864    let text = r#"
10865        a
10866        b
10867        c
10868    "#
10869    .unindent();
10870
10871    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10872    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10873    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10874    editor
10875        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10876        .await;
10877
10878    editor.update_in(cx, |editor, window, cx| {
10879        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10880            s.select_ranges([
10881                Point::new(0, 1)..Point::new(0, 1),
10882                Point::new(1, 1)..Point::new(1, 1),
10883                Point::new(2, 1)..Point::new(2, 1),
10884            ])
10885        });
10886
10887        editor.handle_input("{", window, cx);
10888        editor.handle_input("{", window, cx);
10889        editor.handle_input("_", window, cx);
10890        assert_eq!(
10891            editor.text(cx),
10892            "
10893                a{{_}}
10894                b{{_}}
10895                c{{_}}
10896            "
10897            .unindent()
10898        );
10899        assert_eq!(
10900            editor
10901                .selections
10902                .ranges::<Point>(&editor.display_snapshot(cx)),
10903            [
10904                Point::new(0, 4)..Point::new(0, 4),
10905                Point::new(1, 4)..Point::new(1, 4),
10906                Point::new(2, 4)..Point::new(2, 4)
10907            ]
10908        );
10909
10910        editor.backspace(&Default::default(), window, cx);
10911        editor.backspace(&Default::default(), window, cx);
10912        assert_eq!(
10913            editor.text(cx),
10914            "
10915                a{}
10916                b{}
10917                c{}
10918            "
10919            .unindent()
10920        );
10921        assert_eq!(
10922            editor
10923                .selections
10924                .ranges::<Point>(&editor.display_snapshot(cx)),
10925            [
10926                Point::new(0, 2)..Point::new(0, 2),
10927                Point::new(1, 2)..Point::new(1, 2),
10928                Point::new(2, 2)..Point::new(2, 2)
10929            ]
10930        );
10931
10932        editor.delete_to_previous_word_start(&Default::default(), window, cx);
10933        assert_eq!(
10934            editor.text(cx),
10935            "
10936                a
10937                b
10938                c
10939            "
10940            .unindent()
10941        );
10942        assert_eq!(
10943            editor
10944                .selections
10945                .ranges::<Point>(&editor.display_snapshot(cx)),
10946            [
10947                Point::new(0, 1)..Point::new(0, 1),
10948                Point::new(1, 1)..Point::new(1, 1),
10949                Point::new(2, 1)..Point::new(2, 1)
10950            ]
10951        );
10952    });
10953}
10954
10955#[gpui::test]
10956async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
10957    init_test(cx, |settings| {
10958        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10959    });
10960
10961    let mut cx = EditorTestContext::new(cx).await;
10962
10963    let language = Arc::new(Language::new(
10964        LanguageConfig {
10965            brackets: BracketPairConfig {
10966                pairs: vec![
10967                    BracketPair {
10968                        start: "{".to_string(),
10969                        end: "}".to_string(),
10970                        close: true,
10971                        surround: true,
10972                        newline: true,
10973                    },
10974                    BracketPair {
10975                        start: "(".to_string(),
10976                        end: ")".to_string(),
10977                        close: true,
10978                        surround: true,
10979                        newline: true,
10980                    },
10981                    BracketPair {
10982                        start: "[".to_string(),
10983                        end: "]".to_string(),
10984                        close: false,
10985                        surround: true,
10986                        newline: true,
10987                    },
10988                ],
10989                ..Default::default()
10990            },
10991            autoclose_before: "})]".to_string(),
10992            ..Default::default()
10993        },
10994        Some(tree_sitter_rust::LANGUAGE.into()),
10995    ));
10996
10997    cx.language_registry().add(language.clone());
10998    cx.update_buffer(|buffer, cx| {
10999        buffer.set_language(Some(language), cx);
11000    });
11001
11002    cx.set_state(
11003        &"
11004            {(ˇ)}
11005            [[ˇ]]
11006            {(ˇ)}
11007        "
11008        .unindent(),
11009    );
11010
11011    cx.update_editor(|editor, window, cx| {
11012        editor.backspace(&Default::default(), window, cx);
11013        editor.backspace(&Default::default(), window, cx);
11014    });
11015
11016    cx.assert_editor_state(
11017        &"
11018            ˇ
11019            ˇ]]
11020            ˇ
11021        "
11022        .unindent(),
11023    );
11024
11025    cx.update_editor(|editor, window, cx| {
11026        editor.handle_input("{", window, cx);
11027        editor.handle_input("{", window, cx);
11028        editor.move_right(&MoveRight, window, cx);
11029        editor.move_right(&MoveRight, window, cx);
11030        editor.move_left(&MoveLeft, window, cx);
11031        editor.move_left(&MoveLeft, window, cx);
11032        editor.backspace(&Default::default(), window, cx);
11033    });
11034
11035    cx.assert_editor_state(
11036        &"
11037            {ˇ}
11038            {ˇ}]]
11039            {ˇ}
11040        "
11041        .unindent(),
11042    );
11043
11044    cx.update_editor(|editor, window, cx| {
11045        editor.backspace(&Default::default(), window, cx);
11046    });
11047
11048    cx.assert_editor_state(
11049        &"
11050            ˇ
11051            ˇ]]
11052            ˇ
11053        "
11054        .unindent(),
11055    );
11056}
11057
11058#[gpui::test]
11059async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
11060    init_test(cx, |_| {});
11061
11062    let language = Arc::new(Language::new(
11063        LanguageConfig::default(),
11064        Some(tree_sitter_rust::LANGUAGE.into()),
11065    ));
11066
11067    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
11068    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11069    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11070    editor
11071        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
11072        .await;
11073
11074    editor.update_in(cx, |editor, window, cx| {
11075        editor.set_auto_replace_emoji_shortcode(true);
11076
11077        editor.handle_input("Hello ", window, cx);
11078        editor.handle_input(":wave", window, cx);
11079        assert_eq!(editor.text(cx), "Hello :wave".unindent());
11080
11081        editor.handle_input(":", window, cx);
11082        assert_eq!(editor.text(cx), "Hello 👋".unindent());
11083
11084        editor.handle_input(" :smile", window, cx);
11085        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
11086
11087        editor.handle_input(":", window, cx);
11088        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
11089
11090        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
11091        editor.handle_input(":wave", window, cx);
11092        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
11093
11094        editor.handle_input(":", window, cx);
11095        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
11096
11097        editor.handle_input(":1", window, cx);
11098        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
11099
11100        editor.handle_input(":", window, cx);
11101        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
11102
11103        // Ensure shortcode does not get replaced when it is part of a word
11104        editor.handle_input(" Test:wave", window, cx);
11105        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
11106
11107        editor.handle_input(":", window, cx);
11108        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
11109
11110        editor.set_auto_replace_emoji_shortcode(false);
11111
11112        // Ensure shortcode does not get replaced when auto replace is off
11113        editor.handle_input(" :wave", window, cx);
11114        assert_eq!(
11115            editor.text(cx),
11116            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
11117        );
11118
11119        editor.handle_input(":", window, cx);
11120        assert_eq!(
11121            editor.text(cx),
11122            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
11123        );
11124    });
11125}
11126
11127#[gpui::test]
11128async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
11129    init_test(cx, |_| {});
11130
11131    let (text, insertion_ranges) = marked_text_ranges(
11132        indoc! {"
11133            ˇ
11134        "},
11135        false,
11136    );
11137
11138    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
11139    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11140
11141    _ = editor.update_in(cx, |editor, window, cx| {
11142        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
11143
11144        editor
11145            .insert_snippet(&insertion_ranges, snippet, window, cx)
11146            .unwrap();
11147
11148        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
11149            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
11150            assert_eq!(editor.text(cx), expected_text);
11151            assert_eq!(
11152                editor
11153                    .selections
11154                    .ranges::<usize>(&editor.display_snapshot(cx)),
11155                selection_ranges
11156            );
11157        }
11158
11159        assert(
11160            editor,
11161            cx,
11162            indoc! {"
11163            type «» =•
11164            "},
11165        );
11166
11167        assert!(editor.context_menu_visible(), "There should be a matches");
11168    });
11169}
11170
11171#[gpui::test]
11172async fn test_snippet_tabstop_navigation_with_placeholders(cx: &mut TestAppContext) {
11173    init_test(cx, |_| {});
11174
11175    fn assert_state(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
11176        let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
11177        assert_eq!(editor.text(cx), expected_text);
11178        assert_eq!(
11179            editor
11180                .selections
11181                .ranges::<usize>(&editor.display_snapshot(cx)),
11182            selection_ranges
11183        );
11184    }
11185
11186    let (text, insertion_ranges) = marked_text_ranges(
11187        indoc! {"
11188            ˇ
11189        "},
11190        false,
11191    );
11192
11193    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
11194    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11195
11196    _ = editor.update_in(cx, |editor, window, cx| {
11197        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2; $3").unwrap();
11198
11199        editor
11200            .insert_snippet(&insertion_ranges, snippet, window, cx)
11201            .unwrap();
11202
11203        assert_state(
11204            editor,
11205            cx,
11206            indoc! {"
11207            type «» = ;•
11208            "},
11209        );
11210
11211        assert!(
11212            editor.context_menu_visible(),
11213            "Context menu should be visible for placeholder choices"
11214        );
11215
11216        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11217
11218        assert_state(
11219            editor,
11220            cx,
11221            indoc! {"
11222            type  = «»;•
11223            "},
11224        );
11225
11226        assert!(
11227            !editor.context_menu_visible(),
11228            "Context menu should be hidden after moving to next tabstop"
11229        );
11230
11231        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11232
11233        assert_state(
11234            editor,
11235            cx,
11236            indoc! {"
11237            type  = ; ˇ
11238            "},
11239        );
11240
11241        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11242
11243        assert_state(
11244            editor,
11245            cx,
11246            indoc! {"
11247            type  = ; ˇ
11248            "},
11249        );
11250    });
11251
11252    _ = editor.update_in(cx, |editor, window, cx| {
11253        editor.select_all(&SelectAll, window, cx);
11254        editor.backspace(&Backspace, window, cx);
11255
11256        let snippet = Snippet::parse("fn ${1|,foo,bar|} = ${2:value}; $3").unwrap();
11257        let insertion_ranges = editor
11258            .selections
11259            .all(&editor.display_snapshot(cx))
11260            .iter()
11261            .map(|s| s.range())
11262            .collect::<Vec<_>>();
11263
11264        editor
11265            .insert_snippet(&insertion_ranges, snippet, window, cx)
11266            .unwrap();
11267
11268        assert_state(editor, cx, "fn «» = value;•");
11269
11270        assert!(
11271            editor.context_menu_visible(),
11272            "Context menu should be visible for placeholder choices"
11273        );
11274
11275        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11276
11277        assert_state(editor, cx, "fn  = «valueˇ»;•");
11278
11279        editor.previous_snippet_tabstop(&PreviousSnippetTabstop, window, cx);
11280
11281        assert_state(editor, cx, "fn «» = value;•");
11282
11283        assert!(
11284            editor.context_menu_visible(),
11285            "Context menu should be visible again after returning to first tabstop"
11286        );
11287
11288        editor.previous_snippet_tabstop(&PreviousSnippetTabstop, window, cx);
11289
11290        assert_state(editor, cx, "fn «» = value;•");
11291    });
11292}
11293
11294#[gpui::test]
11295async fn test_snippets(cx: &mut TestAppContext) {
11296    init_test(cx, |_| {});
11297
11298    let mut cx = EditorTestContext::new(cx).await;
11299
11300    cx.set_state(indoc! {"
11301        a.ˇ b
11302        a.ˇ b
11303        a.ˇ b
11304    "});
11305
11306    cx.update_editor(|editor, window, cx| {
11307        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
11308        let insertion_ranges = editor
11309            .selections
11310            .all(&editor.display_snapshot(cx))
11311            .iter()
11312            .map(|s| s.range())
11313            .collect::<Vec<_>>();
11314        editor
11315            .insert_snippet(&insertion_ranges, snippet, window, cx)
11316            .unwrap();
11317    });
11318
11319    cx.assert_editor_state(indoc! {"
11320        a.f(«oneˇ», two, «threeˇ») b
11321        a.f(«oneˇ», two, «threeˇ») b
11322        a.f(«oneˇ», two, «threeˇ») b
11323    "});
11324
11325    // Can't move earlier than the first tab stop
11326    cx.update_editor(|editor, window, cx| {
11327        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
11328    });
11329    cx.assert_editor_state(indoc! {"
11330        a.f(«oneˇ», two, «threeˇ») b
11331        a.f(«oneˇ», two, «threeˇ») b
11332        a.f(«oneˇ», two, «threeˇ») b
11333    "});
11334
11335    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11336    cx.assert_editor_state(indoc! {"
11337        a.f(one, «twoˇ», three) b
11338        a.f(one, «twoˇ», three) b
11339        a.f(one, «twoˇ», three) b
11340    "});
11341
11342    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
11343    cx.assert_editor_state(indoc! {"
11344        a.f(«oneˇ», two, «threeˇ») b
11345        a.f(«oneˇ», two, «threeˇ») b
11346        a.f(«oneˇ», two, «threeˇ») b
11347    "});
11348
11349    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11350    cx.assert_editor_state(indoc! {"
11351        a.f(one, «twoˇ», three) b
11352        a.f(one, «twoˇ», three) b
11353        a.f(one, «twoˇ», three) b
11354    "});
11355    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11356    cx.assert_editor_state(indoc! {"
11357        a.f(one, two, three)ˇ b
11358        a.f(one, two, three)ˇ b
11359        a.f(one, two, three)ˇ b
11360    "});
11361
11362    // As soon as the last tab stop is reached, snippet state is gone
11363    cx.update_editor(|editor, window, cx| {
11364        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
11365    });
11366    cx.assert_editor_state(indoc! {"
11367        a.f(one, two, three)ˇ b
11368        a.f(one, two, three)ˇ b
11369        a.f(one, two, three)ˇ b
11370    "});
11371}
11372
11373#[gpui::test]
11374async fn test_snippet_indentation(cx: &mut TestAppContext) {
11375    init_test(cx, |_| {});
11376
11377    let mut cx = EditorTestContext::new(cx).await;
11378
11379    cx.update_editor(|editor, window, cx| {
11380        let snippet = Snippet::parse(indoc! {"
11381            /*
11382             * Multiline comment with leading indentation
11383             *
11384             * $1
11385             */
11386            $0"})
11387        .unwrap();
11388        let insertion_ranges = editor
11389            .selections
11390            .all(&editor.display_snapshot(cx))
11391            .iter()
11392            .map(|s| s.range())
11393            .collect::<Vec<_>>();
11394        editor
11395            .insert_snippet(&insertion_ranges, snippet, window, cx)
11396            .unwrap();
11397    });
11398
11399    cx.assert_editor_state(indoc! {"
11400        /*
11401         * Multiline comment with leading indentation
11402         *
11403         * ˇ
11404         */
11405    "});
11406
11407    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11408    cx.assert_editor_state(indoc! {"
11409        /*
11410         * Multiline comment with leading indentation
11411         *
11412         *•
11413         */
11414        ˇ"});
11415}
11416
11417#[gpui::test]
11418async fn test_document_format_during_save(cx: &mut TestAppContext) {
11419    init_test(cx, |_| {});
11420
11421    let fs = FakeFs::new(cx.executor());
11422    fs.insert_file(path!("/file.rs"), Default::default()).await;
11423
11424    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
11425
11426    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11427    language_registry.add(rust_lang());
11428    let mut fake_servers = language_registry.register_fake_lsp(
11429        "Rust",
11430        FakeLspAdapter {
11431            capabilities: lsp::ServerCapabilities {
11432                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11433                ..Default::default()
11434            },
11435            ..Default::default()
11436        },
11437    );
11438
11439    let buffer = project
11440        .update(cx, |project, cx| {
11441            project.open_local_buffer(path!("/file.rs"), cx)
11442        })
11443        .await
11444        .unwrap();
11445
11446    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11447    let (editor, cx) = cx.add_window_view(|window, cx| {
11448        build_editor_with_project(project.clone(), buffer, window, cx)
11449    });
11450    editor.update_in(cx, |editor, window, cx| {
11451        editor.set_text("one\ntwo\nthree\n", window, cx)
11452    });
11453    assert!(cx.read(|cx| editor.is_dirty(cx)));
11454
11455    cx.executor().start_waiting();
11456    let fake_server = fake_servers.next().await.unwrap();
11457
11458    {
11459        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11460            move |params, _| async move {
11461                assert_eq!(
11462                    params.text_document.uri,
11463                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11464                );
11465                assert_eq!(params.options.tab_size, 4);
11466                Ok(Some(vec![lsp::TextEdit::new(
11467                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11468                    ", ".to_string(),
11469                )]))
11470            },
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        assert_eq!(
11489            editor.update(cx, |editor, cx| editor.text(cx)),
11490            "one, two\nthree\n"
11491        );
11492        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11493    }
11494
11495    {
11496        editor.update_in(cx, |editor, window, cx| {
11497            editor.set_text("one\ntwo\nthree\n", window, cx)
11498        });
11499        assert!(cx.read(|cx| editor.is_dirty(cx)));
11500
11501        // Ensure we can still save even if formatting hangs.
11502        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11503            move |params, _| async move {
11504                assert_eq!(
11505                    params.text_document.uri,
11506                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11507                );
11508                futures::future::pending::<()>().await;
11509                unreachable!()
11510            },
11511        );
11512        let save = editor
11513            .update_in(cx, |editor, window, cx| {
11514                editor.save(
11515                    SaveOptions {
11516                        format: true,
11517                        autosave: false,
11518                    },
11519                    project.clone(),
11520                    window,
11521                    cx,
11522                )
11523            })
11524            .unwrap();
11525        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11526        cx.executor().start_waiting();
11527        save.await;
11528        assert_eq!(
11529            editor.update(cx, |editor, cx| editor.text(cx)),
11530            "one\ntwo\nthree\n"
11531        );
11532    }
11533
11534    // Set rust language override and assert overridden tabsize is sent to language server
11535    update_test_language_settings(cx, |settings| {
11536        settings.languages.0.insert(
11537            "Rust".into(),
11538            LanguageSettingsContent {
11539                tab_size: NonZeroU32::new(8),
11540                ..Default::default()
11541            },
11542        );
11543    });
11544
11545    {
11546        editor.update_in(cx, |editor, window, cx| {
11547            editor.set_text("somehting_new\n", window, cx)
11548        });
11549        assert!(cx.read(|cx| editor.is_dirty(cx)));
11550        let _formatting_request_signal = fake_server
11551            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
11552                assert_eq!(
11553                    params.text_document.uri,
11554                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11555                );
11556                assert_eq!(params.options.tab_size, 8);
11557                Ok(Some(vec![]))
11558            });
11559        let save = editor
11560            .update_in(cx, |editor, window, cx| {
11561                editor.save(
11562                    SaveOptions {
11563                        format: true,
11564                        autosave: false,
11565                    },
11566                    project.clone(),
11567                    window,
11568                    cx,
11569                )
11570            })
11571            .unwrap();
11572        cx.executor().start_waiting();
11573        save.await;
11574    }
11575}
11576
11577#[gpui::test]
11578async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
11579    init_test(cx, |settings| {
11580        settings.defaults.ensure_final_newline_on_save = Some(false);
11581    });
11582
11583    let fs = FakeFs::new(cx.executor());
11584    fs.insert_file(path!("/file.txt"), "foo".into()).await;
11585
11586    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
11587
11588    let buffer = project
11589        .update(cx, |project, cx| {
11590            project.open_local_buffer(path!("/file.txt"), cx)
11591        })
11592        .await
11593        .unwrap();
11594
11595    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11596    let (editor, cx) = cx.add_window_view(|window, cx| {
11597        build_editor_with_project(project.clone(), buffer, window, cx)
11598    });
11599    editor.update_in(cx, |editor, window, cx| {
11600        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
11601            s.select_ranges([0..0])
11602        });
11603    });
11604    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11605
11606    editor.update_in(cx, |editor, window, cx| {
11607        editor.handle_input("\n", window, cx)
11608    });
11609    cx.run_until_parked();
11610    save(&editor, &project, cx).await;
11611    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11612
11613    editor.update_in(cx, |editor, window, cx| {
11614        editor.undo(&Default::default(), window, cx);
11615    });
11616    save(&editor, &project, cx).await;
11617    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11618
11619    editor.update_in(cx, |editor, window, cx| {
11620        editor.redo(&Default::default(), window, cx);
11621    });
11622    cx.run_until_parked();
11623    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11624
11625    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
11626        let save = editor
11627            .update_in(cx, |editor, window, cx| {
11628                editor.save(
11629                    SaveOptions {
11630                        format: true,
11631                        autosave: false,
11632                    },
11633                    project.clone(),
11634                    window,
11635                    cx,
11636                )
11637            })
11638            .unwrap();
11639        cx.executor().start_waiting();
11640        save.await;
11641        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11642    }
11643}
11644
11645#[gpui::test]
11646async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
11647    init_test(cx, |_| {});
11648
11649    let cols = 4;
11650    let rows = 10;
11651    let sample_text_1 = sample_text(rows, cols, 'a');
11652    assert_eq!(
11653        sample_text_1,
11654        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11655    );
11656    let sample_text_2 = sample_text(rows, cols, 'l');
11657    assert_eq!(
11658        sample_text_2,
11659        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11660    );
11661    let sample_text_3 = sample_text(rows, cols, 'v');
11662    assert_eq!(
11663        sample_text_3,
11664        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11665    );
11666
11667    let fs = FakeFs::new(cx.executor());
11668    fs.insert_tree(
11669        path!("/a"),
11670        json!({
11671            "main.rs": sample_text_1,
11672            "other.rs": sample_text_2,
11673            "lib.rs": sample_text_3,
11674        }),
11675    )
11676    .await;
11677
11678    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11679    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11680    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11681
11682    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11683    language_registry.add(rust_lang());
11684    let mut fake_servers = language_registry.register_fake_lsp(
11685        "Rust",
11686        FakeLspAdapter {
11687            capabilities: lsp::ServerCapabilities {
11688                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11689                ..Default::default()
11690            },
11691            ..Default::default()
11692        },
11693    );
11694
11695    let worktree = project.update(cx, |project, cx| {
11696        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
11697        assert_eq!(worktrees.len(), 1);
11698        worktrees.pop().unwrap()
11699    });
11700    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11701
11702    let buffer_1 = project
11703        .update(cx, |project, cx| {
11704            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
11705        })
11706        .await
11707        .unwrap();
11708    let buffer_2 = project
11709        .update(cx, |project, cx| {
11710            project.open_buffer((worktree_id, rel_path("other.rs")), cx)
11711        })
11712        .await
11713        .unwrap();
11714    let buffer_3 = project
11715        .update(cx, |project, cx| {
11716            project.open_buffer((worktree_id, rel_path("lib.rs")), cx)
11717        })
11718        .await
11719        .unwrap();
11720
11721    let multi_buffer = cx.new(|cx| {
11722        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11723        multi_buffer.push_excerpts(
11724            buffer_1.clone(),
11725            [
11726                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11727                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11728                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11729            ],
11730            cx,
11731        );
11732        multi_buffer.push_excerpts(
11733            buffer_2.clone(),
11734            [
11735                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11736                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11737                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11738            ],
11739            cx,
11740        );
11741        multi_buffer.push_excerpts(
11742            buffer_3.clone(),
11743            [
11744                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11745                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11746                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11747            ],
11748            cx,
11749        );
11750        multi_buffer
11751    });
11752    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
11753        Editor::new(
11754            EditorMode::full(),
11755            multi_buffer,
11756            Some(project.clone()),
11757            window,
11758            cx,
11759        )
11760    });
11761
11762    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11763        editor.change_selections(
11764            SelectionEffects::scroll(Autoscroll::Next),
11765            window,
11766            cx,
11767            |s| s.select_ranges(Some(1..2)),
11768        );
11769        editor.insert("|one|two|three|", window, cx);
11770    });
11771    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11772    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11773        editor.change_selections(
11774            SelectionEffects::scroll(Autoscroll::Next),
11775            window,
11776            cx,
11777            |s| s.select_ranges(Some(60..70)),
11778        );
11779        editor.insert("|four|five|six|", window, cx);
11780    });
11781    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11782
11783    // First two buffers should be edited, but not the third one.
11784    assert_eq!(
11785        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11786        "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}",
11787    );
11788    buffer_1.update(cx, |buffer, _| {
11789        assert!(buffer.is_dirty());
11790        assert_eq!(
11791            buffer.text(),
11792            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
11793        )
11794    });
11795    buffer_2.update(cx, |buffer, _| {
11796        assert!(buffer.is_dirty());
11797        assert_eq!(
11798            buffer.text(),
11799            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
11800        )
11801    });
11802    buffer_3.update(cx, |buffer, _| {
11803        assert!(!buffer.is_dirty());
11804        assert_eq!(buffer.text(), sample_text_3,)
11805    });
11806    cx.executor().run_until_parked();
11807
11808    cx.executor().start_waiting();
11809    let save = multi_buffer_editor
11810        .update_in(cx, |editor, window, cx| {
11811            editor.save(
11812                SaveOptions {
11813                    format: true,
11814                    autosave: false,
11815                },
11816                project.clone(),
11817                window,
11818                cx,
11819            )
11820        })
11821        .unwrap();
11822
11823    let fake_server = fake_servers.next().await.unwrap();
11824    fake_server
11825        .server
11826        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
11827            Ok(Some(vec![lsp::TextEdit::new(
11828                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11829                format!("[{} formatted]", params.text_document.uri),
11830            )]))
11831        })
11832        .detach();
11833    save.await;
11834
11835    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
11836    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
11837    assert_eq!(
11838        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11839        uri!(
11840            "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}"
11841        ),
11842    );
11843    buffer_1.update(cx, |buffer, _| {
11844        assert!(!buffer.is_dirty());
11845        assert_eq!(
11846            buffer.text(),
11847            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
11848        )
11849    });
11850    buffer_2.update(cx, |buffer, _| {
11851        assert!(!buffer.is_dirty());
11852        assert_eq!(
11853            buffer.text(),
11854            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
11855        )
11856    });
11857    buffer_3.update(cx, |buffer, _| {
11858        assert!(!buffer.is_dirty());
11859        assert_eq!(buffer.text(), sample_text_3,)
11860    });
11861}
11862
11863#[gpui::test]
11864async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
11865    init_test(cx, |_| {});
11866
11867    let fs = FakeFs::new(cx.executor());
11868    fs.insert_tree(
11869        path!("/dir"),
11870        json!({
11871            "file1.rs": "fn main() { println!(\"hello\"); }",
11872            "file2.rs": "fn test() { println!(\"test\"); }",
11873            "file3.rs": "fn other() { println!(\"other\"); }\n",
11874        }),
11875    )
11876    .await;
11877
11878    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
11879    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11880    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11881
11882    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11883    language_registry.add(rust_lang());
11884
11885    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
11886    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11887
11888    // Open three buffers
11889    let buffer_1 = project
11890        .update(cx, |project, cx| {
11891            project.open_buffer((worktree_id, rel_path("file1.rs")), cx)
11892        })
11893        .await
11894        .unwrap();
11895    let buffer_2 = project
11896        .update(cx, |project, cx| {
11897            project.open_buffer((worktree_id, rel_path("file2.rs")), cx)
11898        })
11899        .await
11900        .unwrap();
11901    let buffer_3 = project
11902        .update(cx, |project, cx| {
11903            project.open_buffer((worktree_id, rel_path("file3.rs")), cx)
11904        })
11905        .await
11906        .unwrap();
11907
11908    // Create a multi-buffer with all three buffers
11909    let multi_buffer = cx.new(|cx| {
11910        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11911        multi_buffer.push_excerpts(
11912            buffer_1.clone(),
11913            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11914            cx,
11915        );
11916        multi_buffer.push_excerpts(
11917            buffer_2.clone(),
11918            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11919            cx,
11920        );
11921        multi_buffer.push_excerpts(
11922            buffer_3.clone(),
11923            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11924            cx,
11925        );
11926        multi_buffer
11927    });
11928
11929    let editor = cx.new_window_entity(|window, cx| {
11930        Editor::new(
11931            EditorMode::full(),
11932            multi_buffer,
11933            Some(project.clone()),
11934            window,
11935            cx,
11936        )
11937    });
11938
11939    // Edit only the first buffer
11940    editor.update_in(cx, |editor, window, cx| {
11941        editor.change_selections(
11942            SelectionEffects::scroll(Autoscroll::Next),
11943            window,
11944            cx,
11945            |s| s.select_ranges(Some(10..10)),
11946        );
11947        editor.insert("// edited", window, cx);
11948    });
11949
11950    // Verify that only buffer 1 is dirty
11951    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
11952    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11953    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11954
11955    // Get write counts after file creation (files were created with initial content)
11956    // We expect each file to have been written once during creation
11957    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
11958    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
11959    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
11960
11961    // Perform autosave
11962    let save_task = editor.update_in(cx, |editor, window, cx| {
11963        editor.save(
11964            SaveOptions {
11965                format: true,
11966                autosave: true,
11967            },
11968            project.clone(),
11969            window,
11970            cx,
11971        )
11972    });
11973    save_task.await.unwrap();
11974
11975    // Only the dirty buffer should have been saved
11976    assert_eq!(
11977        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11978        1,
11979        "Buffer 1 was dirty, so it should have been written once during autosave"
11980    );
11981    assert_eq!(
11982        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11983        0,
11984        "Buffer 2 was clean, so it should not have been written during autosave"
11985    );
11986    assert_eq!(
11987        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11988        0,
11989        "Buffer 3 was clean, so it should not have been written during autosave"
11990    );
11991
11992    // Verify buffer states after autosave
11993    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11994    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11995    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11996
11997    // Now perform a manual save (format = true)
11998    let save_task = editor.update_in(cx, |editor, window, cx| {
11999        editor.save(
12000            SaveOptions {
12001                format: true,
12002                autosave: false,
12003            },
12004            project.clone(),
12005            window,
12006            cx,
12007        )
12008    });
12009    save_task.await.unwrap();
12010
12011    // During manual save, clean buffers don't get written to disk
12012    // They just get did_save called for language server notifications
12013    assert_eq!(
12014        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
12015        1,
12016        "Buffer 1 should only have been written once total (during autosave, not manual save)"
12017    );
12018    assert_eq!(
12019        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
12020        0,
12021        "Buffer 2 should not have been written at all"
12022    );
12023    assert_eq!(
12024        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
12025        0,
12026        "Buffer 3 should not have been written at all"
12027    );
12028}
12029
12030async fn setup_range_format_test(
12031    cx: &mut TestAppContext,
12032) -> (
12033    Entity<Project>,
12034    Entity<Editor>,
12035    &mut gpui::VisualTestContext,
12036    lsp::FakeLanguageServer,
12037) {
12038    init_test(cx, |_| {});
12039
12040    let fs = FakeFs::new(cx.executor());
12041    fs.insert_file(path!("/file.rs"), Default::default()).await;
12042
12043    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12044
12045    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12046    language_registry.add(rust_lang());
12047    let mut fake_servers = language_registry.register_fake_lsp(
12048        "Rust",
12049        FakeLspAdapter {
12050            capabilities: lsp::ServerCapabilities {
12051                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
12052                ..lsp::ServerCapabilities::default()
12053            },
12054            ..FakeLspAdapter::default()
12055        },
12056    );
12057
12058    let buffer = project
12059        .update(cx, |project, cx| {
12060            project.open_local_buffer(path!("/file.rs"), cx)
12061        })
12062        .await
12063        .unwrap();
12064
12065    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12066    let (editor, cx) = cx.add_window_view(|window, cx| {
12067        build_editor_with_project(project.clone(), buffer, window, cx)
12068    });
12069
12070    cx.executor().start_waiting();
12071    let fake_server = fake_servers.next().await.unwrap();
12072
12073    (project, editor, cx, fake_server)
12074}
12075
12076#[gpui::test]
12077async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
12078    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12079
12080    editor.update_in(cx, |editor, window, cx| {
12081        editor.set_text("one\ntwo\nthree\n", window, cx)
12082    });
12083    assert!(cx.read(|cx| editor.is_dirty(cx)));
12084
12085    let save = editor
12086        .update_in(cx, |editor, window, cx| {
12087            editor.save(
12088                SaveOptions {
12089                    format: true,
12090                    autosave: false,
12091                },
12092                project.clone(),
12093                window,
12094                cx,
12095            )
12096        })
12097        .unwrap();
12098    fake_server
12099        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
12100            assert_eq!(
12101                params.text_document.uri,
12102                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12103            );
12104            assert_eq!(params.options.tab_size, 4);
12105            Ok(Some(vec![lsp::TextEdit::new(
12106                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12107                ", ".to_string(),
12108            )]))
12109        })
12110        .next()
12111        .await;
12112    cx.executor().start_waiting();
12113    save.await;
12114    assert_eq!(
12115        editor.update(cx, |editor, cx| editor.text(cx)),
12116        "one, two\nthree\n"
12117    );
12118    assert!(!cx.read(|cx| editor.is_dirty(cx)));
12119}
12120
12121#[gpui::test]
12122async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
12123    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12124
12125    editor.update_in(cx, |editor, window, cx| {
12126        editor.set_text("one\ntwo\nthree\n", window, cx)
12127    });
12128    assert!(cx.read(|cx| editor.is_dirty(cx)));
12129
12130    // Test that save still works when formatting hangs
12131    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
12132        move |params, _| async move {
12133            assert_eq!(
12134                params.text_document.uri,
12135                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12136            );
12137            futures::future::pending::<()>().await;
12138            unreachable!()
12139        },
12140    );
12141    let save = editor
12142        .update_in(cx, |editor, window, cx| {
12143            editor.save(
12144                SaveOptions {
12145                    format: true,
12146                    autosave: false,
12147                },
12148                project.clone(),
12149                window,
12150                cx,
12151            )
12152        })
12153        .unwrap();
12154    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12155    cx.executor().start_waiting();
12156    save.await;
12157    assert_eq!(
12158        editor.update(cx, |editor, cx| editor.text(cx)),
12159        "one\ntwo\nthree\n"
12160    );
12161    assert!(!cx.read(|cx| editor.is_dirty(cx)));
12162}
12163
12164#[gpui::test]
12165async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
12166    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12167
12168    // Buffer starts clean, no formatting should be requested
12169    let save = editor
12170        .update_in(cx, |editor, window, cx| {
12171            editor.save(
12172                SaveOptions {
12173                    format: false,
12174                    autosave: false,
12175                },
12176                project.clone(),
12177                window,
12178                cx,
12179            )
12180        })
12181        .unwrap();
12182    let _pending_format_request = fake_server
12183        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
12184            panic!("Should not be invoked");
12185        })
12186        .next();
12187    cx.executor().start_waiting();
12188    save.await;
12189    cx.run_until_parked();
12190}
12191
12192#[gpui::test]
12193async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
12194    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12195
12196    // Set Rust language override and assert overridden tabsize is sent to language server
12197    update_test_language_settings(cx, |settings| {
12198        settings.languages.0.insert(
12199            "Rust".into(),
12200            LanguageSettingsContent {
12201                tab_size: NonZeroU32::new(8),
12202                ..Default::default()
12203            },
12204        );
12205    });
12206
12207    editor.update_in(cx, |editor, window, cx| {
12208        editor.set_text("something_new\n", window, cx)
12209    });
12210    assert!(cx.read(|cx| editor.is_dirty(cx)));
12211    let save = editor
12212        .update_in(cx, |editor, window, cx| {
12213            editor.save(
12214                SaveOptions {
12215                    format: true,
12216                    autosave: false,
12217                },
12218                project.clone(),
12219                window,
12220                cx,
12221            )
12222        })
12223        .unwrap();
12224    fake_server
12225        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
12226            assert_eq!(
12227                params.text_document.uri,
12228                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12229            );
12230            assert_eq!(params.options.tab_size, 8);
12231            Ok(Some(Vec::new()))
12232        })
12233        .next()
12234        .await;
12235    save.await;
12236}
12237
12238#[gpui::test]
12239async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
12240    init_test(cx, |settings| {
12241        settings.defaults.formatter = Some(FormatterList::Single(Formatter::LanguageServer(
12242            settings::LanguageServerFormatterSpecifier::Current,
12243        )))
12244    });
12245
12246    let fs = FakeFs::new(cx.executor());
12247    fs.insert_file(path!("/file.rs"), Default::default()).await;
12248
12249    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12250
12251    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12252    language_registry.add(Arc::new(Language::new(
12253        LanguageConfig {
12254            name: "Rust".into(),
12255            matcher: LanguageMatcher {
12256                path_suffixes: vec!["rs".to_string()],
12257                ..Default::default()
12258            },
12259            ..LanguageConfig::default()
12260        },
12261        Some(tree_sitter_rust::LANGUAGE.into()),
12262    )));
12263    update_test_language_settings(cx, |settings| {
12264        // Enable Prettier formatting for the same buffer, and ensure
12265        // LSP is called instead of Prettier.
12266        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12267    });
12268    let mut fake_servers = language_registry.register_fake_lsp(
12269        "Rust",
12270        FakeLspAdapter {
12271            capabilities: lsp::ServerCapabilities {
12272                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12273                ..Default::default()
12274            },
12275            ..Default::default()
12276        },
12277    );
12278
12279    let buffer = project
12280        .update(cx, |project, cx| {
12281            project.open_local_buffer(path!("/file.rs"), cx)
12282        })
12283        .await
12284        .unwrap();
12285
12286    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12287    let (editor, cx) = cx.add_window_view(|window, cx| {
12288        build_editor_with_project(project.clone(), buffer, window, cx)
12289    });
12290    editor.update_in(cx, |editor, window, cx| {
12291        editor.set_text("one\ntwo\nthree\n", window, cx)
12292    });
12293
12294    cx.executor().start_waiting();
12295    let fake_server = fake_servers.next().await.unwrap();
12296
12297    let format = editor
12298        .update_in(cx, |editor, window, cx| {
12299            editor.perform_format(
12300                project.clone(),
12301                FormatTrigger::Manual,
12302                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12303                window,
12304                cx,
12305            )
12306        })
12307        .unwrap();
12308    fake_server
12309        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
12310            assert_eq!(
12311                params.text_document.uri,
12312                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12313            );
12314            assert_eq!(params.options.tab_size, 4);
12315            Ok(Some(vec![lsp::TextEdit::new(
12316                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12317                ", ".to_string(),
12318            )]))
12319        })
12320        .next()
12321        .await;
12322    cx.executor().start_waiting();
12323    format.await;
12324    assert_eq!(
12325        editor.update(cx, |editor, cx| editor.text(cx)),
12326        "one, two\nthree\n"
12327    );
12328
12329    editor.update_in(cx, |editor, window, cx| {
12330        editor.set_text("one\ntwo\nthree\n", window, cx)
12331    });
12332    // Ensure we don't lock if formatting hangs.
12333    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12334        move |params, _| async move {
12335            assert_eq!(
12336                params.text_document.uri,
12337                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12338            );
12339            futures::future::pending::<()>().await;
12340            unreachable!()
12341        },
12342    );
12343    let format = editor
12344        .update_in(cx, |editor, window, cx| {
12345            editor.perform_format(
12346                project,
12347                FormatTrigger::Manual,
12348                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12349                window,
12350                cx,
12351            )
12352        })
12353        .unwrap();
12354    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12355    cx.executor().start_waiting();
12356    format.await;
12357    assert_eq!(
12358        editor.update(cx, |editor, cx| editor.text(cx)),
12359        "one\ntwo\nthree\n"
12360    );
12361}
12362
12363#[gpui::test]
12364async fn test_multiple_formatters(cx: &mut TestAppContext) {
12365    init_test(cx, |settings| {
12366        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
12367        settings.defaults.formatter = Some(FormatterList::Vec(vec![
12368            Formatter::LanguageServer(settings::LanguageServerFormatterSpecifier::Current),
12369            Formatter::CodeAction("code-action-1".into()),
12370            Formatter::CodeAction("code-action-2".into()),
12371        ]))
12372    });
12373
12374    let fs = FakeFs::new(cx.executor());
12375    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
12376        .await;
12377
12378    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12379    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12380    language_registry.add(rust_lang());
12381
12382    let mut fake_servers = language_registry.register_fake_lsp(
12383        "Rust",
12384        FakeLspAdapter {
12385            capabilities: lsp::ServerCapabilities {
12386                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12387                execute_command_provider: Some(lsp::ExecuteCommandOptions {
12388                    commands: vec!["the-command-for-code-action-1".into()],
12389                    ..Default::default()
12390                }),
12391                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12392                ..Default::default()
12393            },
12394            ..Default::default()
12395        },
12396    );
12397
12398    let buffer = project
12399        .update(cx, |project, cx| {
12400            project.open_local_buffer(path!("/file.rs"), cx)
12401        })
12402        .await
12403        .unwrap();
12404
12405    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12406    let (editor, cx) = cx.add_window_view(|window, cx| {
12407        build_editor_with_project(project.clone(), buffer, window, cx)
12408    });
12409
12410    cx.executor().start_waiting();
12411
12412    let fake_server = fake_servers.next().await.unwrap();
12413    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12414        move |_params, _| async move {
12415            Ok(Some(vec![lsp::TextEdit::new(
12416                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12417                "applied-formatting\n".to_string(),
12418            )]))
12419        },
12420    );
12421    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12422        move |params, _| async move {
12423            let requested_code_actions = params.context.only.expect("Expected code action request");
12424            assert_eq!(requested_code_actions.len(), 1);
12425
12426            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
12427            let code_action = match requested_code_actions[0].as_str() {
12428                "code-action-1" => lsp::CodeAction {
12429                    kind: Some("code-action-1".into()),
12430                    edit: Some(lsp::WorkspaceEdit::new(
12431                        [(
12432                            uri,
12433                            vec![lsp::TextEdit::new(
12434                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12435                                "applied-code-action-1-edit\n".to_string(),
12436                            )],
12437                        )]
12438                        .into_iter()
12439                        .collect(),
12440                    )),
12441                    command: Some(lsp::Command {
12442                        command: "the-command-for-code-action-1".into(),
12443                        ..Default::default()
12444                    }),
12445                    ..Default::default()
12446                },
12447                "code-action-2" => lsp::CodeAction {
12448                    kind: Some("code-action-2".into()),
12449                    edit: Some(lsp::WorkspaceEdit::new(
12450                        [(
12451                            uri,
12452                            vec![lsp::TextEdit::new(
12453                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12454                                "applied-code-action-2-edit\n".to_string(),
12455                            )],
12456                        )]
12457                        .into_iter()
12458                        .collect(),
12459                    )),
12460                    ..Default::default()
12461                },
12462                req => panic!("Unexpected code action request: {:?}", req),
12463            };
12464            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12465                code_action,
12466            )]))
12467        },
12468    );
12469
12470    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
12471        move |params, _| async move { Ok(params) }
12472    });
12473
12474    let command_lock = Arc::new(futures::lock::Mutex::new(()));
12475    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
12476        let fake = fake_server.clone();
12477        let lock = command_lock.clone();
12478        move |params, _| {
12479            assert_eq!(params.command, "the-command-for-code-action-1");
12480            let fake = fake.clone();
12481            let lock = lock.clone();
12482            async move {
12483                lock.lock().await;
12484                fake.server
12485                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
12486                        label: None,
12487                        edit: lsp::WorkspaceEdit {
12488                            changes: Some(
12489                                [(
12490                                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
12491                                    vec![lsp::TextEdit {
12492                                        range: lsp::Range::new(
12493                                            lsp::Position::new(0, 0),
12494                                            lsp::Position::new(0, 0),
12495                                        ),
12496                                        new_text: "applied-code-action-1-command\n".into(),
12497                                    }],
12498                                )]
12499                                .into_iter()
12500                                .collect(),
12501                            ),
12502                            ..Default::default()
12503                        },
12504                    })
12505                    .await
12506                    .into_response()
12507                    .unwrap();
12508                Ok(Some(json!(null)))
12509            }
12510        }
12511    });
12512
12513    cx.executor().start_waiting();
12514    editor
12515        .update_in(cx, |editor, window, cx| {
12516            editor.perform_format(
12517                project.clone(),
12518                FormatTrigger::Manual,
12519                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12520                window,
12521                cx,
12522            )
12523        })
12524        .unwrap()
12525        .await;
12526    editor.update(cx, |editor, cx| {
12527        assert_eq!(
12528            editor.text(cx),
12529            r#"
12530                applied-code-action-2-edit
12531                applied-code-action-1-command
12532                applied-code-action-1-edit
12533                applied-formatting
12534                one
12535                two
12536                three
12537            "#
12538            .unindent()
12539        );
12540    });
12541
12542    editor.update_in(cx, |editor, window, cx| {
12543        editor.undo(&Default::default(), window, cx);
12544        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12545    });
12546
12547    // Perform a manual edit while waiting for an LSP command
12548    // that's being run as part of a formatting code action.
12549    let lock_guard = command_lock.lock().await;
12550    let format = editor
12551        .update_in(cx, |editor, window, cx| {
12552            editor.perform_format(
12553                project.clone(),
12554                FormatTrigger::Manual,
12555                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12556                window,
12557                cx,
12558            )
12559        })
12560        .unwrap();
12561    cx.run_until_parked();
12562    editor.update(cx, |editor, cx| {
12563        assert_eq!(
12564            editor.text(cx),
12565            r#"
12566                applied-code-action-1-edit
12567                applied-formatting
12568                one
12569                two
12570                three
12571            "#
12572            .unindent()
12573        );
12574
12575        editor.buffer.update(cx, |buffer, cx| {
12576            let ix = buffer.len(cx);
12577            buffer.edit([(ix..ix, "edited\n")], None, cx);
12578        });
12579    });
12580
12581    // Allow the LSP command to proceed. Because the buffer was edited,
12582    // the second code action will not be run.
12583    drop(lock_guard);
12584    format.await;
12585    editor.update_in(cx, |editor, window, cx| {
12586        assert_eq!(
12587            editor.text(cx),
12588            r#"
12589                applied-code-action-1-command
12590                applied-code-action-1-edit
12591                applied-formatting
12592                one
12593                two
12594                three
12595                edited
12596            "#
12597            .unindent()
12598        );
12599
12600        // The manual edit is undone first, because it is the last thing the user did
12601        // (even though the command completed afterwards).
12602        editor.undo(&Default::default(), window, cx);
12603        assert_eq!(
12604            editor.text(cx),
12605            r#"
12606                applied-code-action-1-command
12607                applied-code-action-1-edit
12608                applied-formatting
12609                one
12610                two
12611                three
12612            "#
12613            .unindent()
12614        );
12615
12616        // All the formatting (including the command, which completed after the manual edit)
12617        // is undone together.
12618        editor.undo(&Default::default(), window, cx);
12619        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12620    });
12621}
12622
12623#[gpui::test]
12624async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
12625    init_test(cx, |settings| {
12626        settings.defaults.formatter = Some(FormatterList::Vec(vec![Formatter::LanguageServer(
12627            settings::LanguageServerFormatterSpecifier::Current,
12628        )]))
12629    });
12630
12631    let fs = FakeFs::new(cx.executor());
12632    fs.insert_file(path!("/file.ts"), Default::default()).await;
12633
12634    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12635
12636    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12637    language_registry.add(Arc::new(Language::new(
12638        LanguageConfig {
12639            name: "TypeScript".into(),
12640            matcher: LanguageMatcher {
12641                path_suffixes: vec!["ts".to_string()],
12642                ..Default::default()
12643            },
12644            ..LanguageConfig::default()
12645        },
12646        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12647    )));
12648    update_test_language_settings(cx, |settings| {
12649        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12650    });
12651    let mut fake_servers = language_registry.register_fake_lsp(
12652        "TypeScript",
12653        FakeLspAdapter {
12654            capabilities: lsp::ServerCapabilities {
12655                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12656                ..Default::default()
12657            },
12658            ..Default::default()
12659        },
12660    );
12661
12662    let buffer = project
12663        .update(cx, |project, cx| {
12664            project.open_local_buffer(path!("/file.ts"), cx)
12665        })
12666        .await
12667        .unwrap();
12668
12669    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12670    let (editor, cx) = cx.add_window_view(|window, cx| {
12671        build_editor_with_project(project.clone(), buffer, window, cx)
12672    });
12673    editor.update_in(cx, |editor, window, cx| {
12674        editor.set_text(
12675            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12676            window,
12677            cx,
12678        )
12679    });
12680
12681    cx.executor().start_waiting();
12682    let fake_server = fake_servers.next().await.unwrap();
12683
12684    let format = editor
12685        .update_in(cx, |editor, window, cx| {
12686            editor.perform_code_action_kind(
12687                project.clone(),
12688                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12689                window,
12690                cx,
12691            )
12692        })
12693        .unwrap();
12694    fake_server
12695        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
12696            assert_eq!(
12697                params.text_document.uri,
12698                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12699            );
12700            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12701                lsp::CodeAction {
12702                    title: "Organize Imports".to_string(),
12703                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
12704                    edit: Some(lsp::WorkspaceEdit {
12705                        changes: Some(
12706                            [(
12707                                params.text_document.uri.clone(),
12708                                vec![lsp::TextEdit::new(
12709                                    lsp::Range::new(
12710                                        lsp::Position::new(1, 0),
12711                                        lsp::Position::new(2, 0),
12712                                    ),
12713                                    "".to_string(),
12714                                )],
12715                            )]
12716                            .into_iter()
12717                            .collect(),
12718                        ),
12719                        ..Default::default()
12720                    }),
12721                    ..Default::default()
12722                },
12723            )]))
12724        })
12725        .next()
12726        .await;
12727    cx.executor().start_waiting();
12728    format.await;
12729    assert_eq!(
12730        editor.update(cx, |editor, cx| editor.text(cx)),
12731        "import { a } from 'module';\n\nconst x = a;\n"
12732    );
12733
12734    editor.update_in(cx, |editor, window, cx| {
12735        editor.set_text(
12736            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12737            window,
12738            cx,
12739        )
12740    });
12741    // Ensure we don't lock if code action hangs.
12742    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12743        move |params, _| async move {
12744            assert_eq!(
12745                params.text_document.uri,
12746                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12747            );
12748            futures::future::pending::<()>().await;
12749            unreachable!()
12750        },
12751    );
12752    let format = editor
12753        .update_in(cx, |editor, window, cx| {
12754            editor.perform_code_action_kind(
12755                project,
12756                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12757                window,
12758                cx,
12759            )
12760        })
12761        .unwrap();
12762    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
12763    cx.executor().start_waiting();
12764    format.await;
12765    assert_eq!(
12766        editor.update(cx, |editor, cx| editor.text(cx)),
12767        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
12768    );
12769}
12770
12771#[gpui::test]
12772async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
12773    init_test(cx, |_| {});
12774
12775    let mut cx = EditorLspTestContext::new_rust(
12776        lsp::ServerCapabilities {
12777            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12778            ..Default::default()
12779        },
12780        cx,
12781    )
12782    .await;
12783
12784    cx.set_state(indoc! {"
12785        one.twoˇ
12786    "});
12787
12788    // The format request takes a long time. When it completes, it inserts
12789    // a newline and an indent before the `.`
12790    cx.lsp
12791        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
12792            let executor = cx.background_executor().clone();
12793            async move {
12794                executor.timer(Duration::from_millis(100)).await;
12795                Ok(Some(vec![lsp::TextEdit {
12796                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
12797                    new_text: "\n    ".into(),
12798                }]))
12799            }
12800        });
12801
12802    // Submit a format request.
12803    let format_1 = cx
12804        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12805        .unwrap();
12806    cx.executor().run_until_parked();
12807
12808    // Submit a second format request.
12809    let format_2 = cx
12810        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12811        .unwrap();
12812    cx.executor().run_until_parked();
12813
12814    // Wait for both format requests to complete
12815    cx.executor().advance_clock(Duration::from_millis(200));
12816    cx.executor().start_waiting();
12817    format_1.await.unwrap();
12818    cx.executor().start_waiting();
12819    format_2.await.unwrap();
12820
12821    // The formatting edits only happens once.
12822    cx.assert_editor_state(indoc! {"
12823        one
12824            .twoˇ
12825    "});
12826}
12827
12828#[gpui::test]
12829async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
12830    init_test(cx, |settings| {
12831        settings.defaults.formatter = Some(FormatterList::default())
12832    });
12833
12834    let mut cx = EditorLspTestContext::new_rust(
12835        lsp::ServerCapabilities {
12836            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12837            ..Default::default()
12838        },
12839        cx,
12840    )
12841    .await;
12842
12843    // Record which buffer changes have been sent to the language server
12844    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
12845    cx.lsp
12846        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
12847            let buffer_changes = buffer_changes.clone();
12848            move |params, _| {
12849                buffer_changes.lock().extend(
12850                    params
12851                        .content_changes
12852                        .into_iter()
12853                        .map(|e| (e.range.unwrap(), e.text)),
12854                );
12855            }
12856        });
12857    // Handle formatting requests to the language server.
12858    cx.lsp
12859        .set_request_handler::<lsp::request::Formatting, _, _>({
12860            let buffer_changes = buffer_changes.clone();
12861            move |_, _| {
12862                let buffer_changes = buffer_changes.clone();
12863                // Insert blank lines between each line of the buffer.
12864                async move {
12865                    // When formatting is requested, trailing whitespace has already been stripped,
12866                    // and the trailing newline has already been added.
12867                    assert_eq!(
12868                        &buffer_changes.lock()[1..],
12869                        &[
12870                            (
12871                                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
12872                                "".into()
12873                            ),
12874                            (
12875                                lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
12876                                "".into()
12877                            ),
12878                            (
12879                                lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
12880                                "\n".into()
12881                            ),
12882                        ]
12883                    );
12884
12885                    Ok(Some(vec![
12886                        lsp::TextEdit {
12887                            range: lsp::Range::new(
12888                                lsp::Position::new(1, 0),
12889                                lsp::Position::new(1, 0),
12890                            ),
12891                            new_text: "\n".into(),
12892                        },
12893                        lsp::TextEdit {
12894                            range: lsp::Range::new(
12895                                lsp::Position::new(2, 0),
12896                                lsp::Position::new(2, 0),
12897                            ),
12898                            new_text: "\n".into(),
12899                        },
12900                    ]))
12901                }
12902            }
12903        });
12904
12905    // Set up a buffer white some trailing whitespace and no trailing newline.
12906    cx.set_state(
12907        &[
12908            "one ",   //
12909            "twoˇ",   //
12910            "three ", //
12911            "four",   //
12912        ]
12913        .join("\n"),
12914    );
12915    cx.run_until_parked();
12916
12917    // Submit a format request.
12918    let format = cx
12919        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12920        .unwrap();
12921
12922    cx.run_until_parked();
12923    // After formatting the buffer, the trailing whitespace is stripped,
12924    // a newline is appended, and the edits provided by the language server
12925    // have been applied.
12926    format.await.unwrap();
12927
12928    cx.assert_editor_state(
12929        &[
12930            "one",   //
12931            "",      //
12932            "twoˇ",  //
12933            "",      //
12934            "three", //
12935            "four",  //
12936            "",      //
12937        ]
12938        .join("\n"),
12939    );
12940
12941    // Undoing the formatting undoes the trailing whitespace removal, the
12942    // trailing newline, and the LSP edits.
12943    cx.update_buffer(|buffer, cx| buffer.undo(cx));
12944    cx.assert_editor_state(
12945        &[
12946            "one ",   //
12947            "twoˇ",   //
12948            "three ", //
12949            "four",   //
12950        ]
12951        .join("\n"),
12952    );
12953}
12954
12955#[gpui::test]
12956async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
12957    cx: &mut TestAppContext,
12958) {
12959    init_test(cx, |_| {});
12960
12961    cx.update(|cx| {
12962        cx.update_global::<SettingsStore, _>(|settings, cx| {
12963            settings.update_user_settings(cx, |settings| {
12964                settings.editor.auto_signature_help = Some(true);
12965            });
12966        });
12967    });
12968
12969    let mut cx = EditorLspTestContext::new_rust(
12970        lsp::ServerCapabilities {
12971            signature_help_provider: Some(lsp::SignatureHelpOptions {
12972                ..Default::default()
12973            }),
12974            ..Default::default()
12975        },
12976        cx,
12977    )
12978    .await;
12979
12980    let language = Language::new(
12981        LanguageConfig {
12982            name: "Rust".into(),
12983            brackets: BracketPairConfig {
12984                pairs: vec![
12985                    BracketPair {
12986                        start: "{".to_string(),
12987                        end: "}".to_string(),
12988                        close: true,
12989                        surround: true,
12990                        newline: true,
12991                    },
12992                    BracketPair {
12993                        start: "(".to_string(),
12994                        end: ")".to_string(),
12995                        close: true,
12996                        surround: true,
12997                        newline: true,
12998                    },
12999                    BracketPair {
13000                        start: "/*".to_string(),
13001                        end: " */".to_string(),
13002                        close: true,
13003                        surround: true,
13004                        newline: true,
13005                    },
13006                    BracketPair {
13007                        start: "[".to_string(),
13008                        end: "]".to_string(),
13009                        close: false,
13010                        surround: false,
13011                        newline: true,
13012                    },
13013                    BracketPair {
13014                        start: "\"".to_string(),
13015                        end: "\"".to_string(),
13016                        close: true,
13017                        surround: true,
13018                        newline: false,
13019                    },
13020                    BracketPair {
13021                        start: "<".to_string(),
13022                        end: ">".to_string(),
13023                        close: false,
13024                        surround: true,
13025                        newline: true,
13026                    },
13027                ],
13028                ..Default::default()
13029            },
13030            autoclose_before: "})]".to_string(),
13031            ..Default::default()
13032        },
13033        Some(tree_sitter_rust::LANGUAGE.into()),
13034    );
13035    let language = Arc::new(language);
13036
13037    cx.language_registry().add(language.clone());
13038    cx.update_buffer(|buffer, cx| {
13039        buffer.set_language(Some(language), cx);
13040    });
13041
13042    cx.set_state(
13043        &r#"
13044            fn main() {
13045                sampleˇ
13046            }
13047        "#
13048        .unindent(),
13049    );
13050
13051    cx.update_editor(|editor, window, cx| {
13052        editor.handle_input("(", window, cx);
13053    });
13054    cx.assert_editor_state(
13055        &"
13056            fn main() {
13057                sample(ˇ)
13058            }
13059        "
13060        .unindent(),
13061    );
13062
13063    let mocked_response = lsp::SignatureHelp {
13064        signatures: vec![lsp::SignatureInformation {
13065            label: "fn sample(param1: u8, param2: u8)".to_string(),
13066            documentation: None,
13067            parameters: Some(vec![
13068                lsp::ParameterInformation {
13069                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13070                    documentation: None,
13071                },
13072                lsp::ParameterInformation {
13073                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13074                    documentation: None,
13075                },
13076            ]),
13077            active_parameter: None,
13078        }],
13079        active_signature: Some(0),
13080        active_parameter: Some(0),
13081    };
13082    handle_signature_help_request(&mut cx, mocked_response).await;
13083
13084    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13085        .await;
13086
13087    cx.editor(|editor, _, _| {
13088        let signature_help_state = editor.signature_help_state.popover().cloned();
13089        let signature = signature_help_state.unwrap();
13090        assert_eq!(
13091            signature.signatures[signature.current_signature].label,
13092            "fn sample(param1: u8, param2: u8)"
13093        );
13094    });
13095}
13096
13097#[gpui::test]
13098async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
13099    init_test(cx, |_| {});
13100
13101    cx.update(|cx| {
13102        cx.update_global::<SettingsStore, _>(|settings, cx| {
13103            settings.update_user_settings(cx, |settings| {
13104                settings.editor.auto_signature_help = Some(false);
13105                settings.editor.show_signature_help_after_edits = Some(false);
13106            });
13107        });
13108    });
13109
13110    let mut cx = EditorLspTestContext::new_rust(
13111        lsp::ServerCapabilities {
13112            signature_help_provider: Some(lsp::SignatureHelpOptions {
13113                ..Default::default()
13114            }),
13115            ..Default::default()
13116        },
13117        cx,
13118    )
13119    .await;
13120
13121    let language = Language::new(
13122        LanguageConfig {
13123            name: "Rust".into(),
13124            brackets: BracketPairConfig {
13125                pairs: vec![
13126                    BracketPair {
13127                        start: "{".to_string(),
13128                        end: "}".to_string(),
13129                        close: true,
13130                        surround: true,
13131                        newline: true,
13132                    },
13133                    BracketPair {
13134                        start: "(".to_string(),
13135                        end: ")".to_string(),
13136                        close: true,
13137                        surround: true,
13138                        newline: true,
13139                    },
13140                    BracketPair {
13141                        start: "/*".to_string(),
13142                        end: " */".to_string(),
13143                        close: true,
13144                        surround: true,
13145                        newline: true,
13146                    },
13147                    BracketPair {
13148                        start: "[".to_string(),
13149                        end: "]".to_string(),
13150                        close: false,
13151                        surround: false,
13152                        newline: true,
13153                    },
13154                    BracketPair {
13155                        start: "\"".to_string(),
13156                        end: "\"".to_string(),
13157                        close: true,
13158                        surround: true,
13159                        newline: false,
13160                    },
13161                    BracketPair {
13162                        start: "<".to_string(),
13163                        end: ">".to_string(),
13164                        close: false,
13165                        surround: true,
13166                        newline: true,
13167                    },
13168                ],
13169                ..Default::default()
13170            },
13171            autoclose_before: "})]".to_string(),
13172            ..Default::default()
13173        },
13174        Some(tree_sitter_rust::LANGUAGE.into()),
13175    );
13176    let language = Arc::new(language);
13177
13178    cx.language_registry().add(language.clone());
13179    cx.update_buffer(|buffer, cx| {
13180        buffer.set_language(Some(language), cx);
13181    });
13182
13183    // Ensure that signature_help is not called when no signature help is enabled.
13184    cx.set_state(
13185        &r#"
13186            fn main() {
13187                sampleˇ
13188            }
13189        "#
13190        .unindent(),
13191    );
13192    cx.update_editor(|editor, window, cx| {
13193        editor.handle_input("(", window, cx);
13194    });
13195    cx.assert_editor_state(
13196        &"
13197            fn main() {
13198                sample(ˇ)
13199            }
13200        "
13201        .unindent(),
13202    );
13203    cx.editor(|editor, _, _| {
13204        assert!(editor.signature_help_state.task().is_none());
13205    });
13206
13207    let mocked_response = lsp::SignatureHelp {
13208        signatures: vec![lsp::SignatureInformation {
13209            label: "fn sample(param1: u8, param2: u8)".to_string(),
13210            documentation: None,
13211            parameters: Some(vec![
13212                lsp::ParameterInformation {
13213                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13214                    documentation: None,
13215                },
13216                lsp::ParameterInformation {
13217                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13218                    documentation: None,
13219                },
13220            ]),
13221            active_parameter: None,
13222        }],
13223        active_signature: Some(0),
13224        active_parameter: Some(0),
13225    };
13226
13227    // Ensure that signature_help is called when enabled afte edits
13228    cx.update(|_, cx| {
13229        cx.update_global::<SettingsStore, _>(|settings, cx| {
13230            settings.update_user_settings(cx, |settings| {
13231                settings.editor.auto_signature_help = Some(false);
13232                settings.editor.show_signature_help_after_edits = Some(true);
13233            });
13234        });
13235    });
13236    cx.set_state(
13237        &r#"
13238            fn main() {
13239                sampleˇ
13240            }
13241        "#
13242        .unindent(),
13243    );
13244    cx.update_editor(|editor, window, cx| {
13245        editor.handle_input("(", window, cx);
13246    });
13247    cx.assert_editor_state(
13248        &"
13249            fn main() {
13250                sample(ˇ)
13251            }
13252        "
13253        .unindent(),
13254    );
13255    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13256    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13257        .await;
13258    cx.update_editor(|editor, _, _| {
13259        let signature_help_state = editor.signature_help_state.popover().cloned();
13260        assert!(signature_help_state.is_some());
13261        let signature = signature_help_state.unwrap();
13262        assert_eq!(
13263            signature.signatures[signature.current_signature].label,
13264            "fn sample(param1: u8, param2: u8)"
13265        );
13266        editor.signature_help_state = SignatureHelpState::default();
13267    });
13268
13269    // Ensure that signature_help is called when auto signature help override is enabled
13270    cx.update(|_, cx| {
13271        cx.update_global::<SettingsStore, _>(|settings, cx| {
13272            settings.update_user_settings(cx, |settings| {
13273                settings.editor.auto_signature_help = Some(true);
13274                settings.editor.show_signature_help_after_edits = Some(false);
13275            });
13276        });
13277    });
13278    cx.set_state(
13279        &r#"
13280            fn main() {
13281                sampleˇ
13282            }
13283        "#
13284        .unindent(),
13285    );
13286    cx.update_editor(|editor, window, cx| {
13287        editor.handle_input("(", window, cx);
13288    });
13289    cx.assert_editor_state(
13290        &"
13291            fn main() {
13292                sample(ˇ)
13293            }
13294        "
13295        .unindent(),
13296    );
13297    handle_signature_help_request(&mut cx, mocked_response).await;
13298    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13299        .await;
13300    cx.editor(|editor, _, _| {
13301        let signature_help_state = editor.signature_help_state.popover().cloned();
13302        assert!(signature_help_state.is_some());
13303        let signature = signature_help_state.unwrap();
13304        assert_eq!(
13305            signature.signatures[signature.current_signature].label,
13306            "fn sample(param1: u8, param2: u8)"
13307        );
13308    });
13309}
13310
13311#[gpui::test]
13312async fn test_signature_help(cx: &mut TestAppContext) {
13313    init_test(cx, |_| {});
13314    cx.update(|cx| {
13315        cx.update_global::<SettingsStore, _>(|settings, cx| {
13316            settings.update_user_settings(cx, |settings| {
13317                settings.editor.auto_signature_help = Some(true);
13318            });
13319        });
13320    });
13321
13322    let mut cx = EditorLspTestContext::new_rust(
13323        lsp::ServerCapabilities {
13324            signature_help_provider: Some(lsp::SignatureHelpOptions {
13325                ..Default::default()
13326            }),
13327            ..Default::default()
13328        },
13329        cx,
13330    )
13331    .await;
13332
13333    // A test that directly calls `show_signature_help`
13334    cx.update_editor(|editor, window, cx| {
13335        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13336    });
13337
13338    let mocked_response = lsp::SignatureHelp {
13339        signatures: vec![lsp::SignatureInformation {
13340            label: "fn sample(param1: u8, param2: u8)".to_string(),
13341            documentation: None,
13342            parameters: Some(vec![
13343                lsp::ParameterInformation {
13344                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13345                    documentation: None,
13346                },
13347                lsp::ParameterInformation {
13348                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13349                    documentation: None,
13350                },
13351            ]),
13352            active_parameter: None,
13353        }],
13354        active_signature: Some(0),
13355        active_parameter: Some(0),
13356    };
13357    handle_signature_help_request(&mut cx, mocked_response).await;
13358
13359    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13360        .await;
13361
13362    cx.editor(|editor, _, _| {
13363        let signature_help_state = editor.signature_help_state.popover().cloned();
13364        assert!(signature_help_state.is_some());
13365        let signature = signature_help_state.unwrap();
13366        assert_eq!(
13367            signature.signatures[signature.current_signature].label,
13368            "fn sample(param1: u8, param2: u8)"
13369        );
13370    });
13371
13372    // When exiting outside from inside the brackets, `signature_help` is closed.
13373    cx.set_state(indoc! {"
13374        fn main() {
13375            sample(ˇ);
13376        }
13377
13378        fn sample(param1: u8, param2: u8) {}
13379    "});
13380
13381    cx.update_editor(|editor, window, cx| {
13382        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13383            s.select_ranges([0..0])
13384        });
13385    });
13386
13387    let mocked_response = lsp::SignatureHelp {
13388        signatures: Vec::new(),
13389        active_signature: None,
13390        active_parameter: None,
13391    };
13392    handle_signature_help_request(&mut cx, mocked_response).await;
13393
13394    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13395        .await;
13396
13397    cx.editor(|editor, _, _| {
13398        assert!(!editor.signature_help_state.is_shown());
13399    });
13400
13401    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
13402    cx.set_state(indoc! {"
13403        fn main() {
13404            sample(ˇ);
13405        }
13406
13407        fn sample(param1: u8, param2: u8) {}
13408    "});
13409
13410    let mocked_response = lsp::SignatureHelp {
13411        signatures: vec![lsp::SignatureInformation {
13412            label: "fn sample(param1: u8, param2: u8)".to_string(),
13413            documentation: None,
13414            parameters: Some(vec![
13415                lsp::ParameterInformation {
13416                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13417                    documentation: None,
13418                },
13419                lsp::ParameterInformation {
13420                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13421                    documentation: None,
13422                },
13423            ]),
13424            active_parameter: None,
13425        }],
13426        active_signature: Some(0),
13427        active_parameter: Some(0),
13428    };
13429    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13430    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13431        .await;
13432    cx.editor(|editor, _, _| {
13433        assert!(editor.signature_help_state.is_shown());
13434    });
13435
13436    // Restore the popover with more parameter input
13437    cx.set_state(indoc! {"
13438        fn main() {
13439            sample(param1, param2ˇ);
13440        }
13441
13442        fn sample(param1: u8, param2: u8) {}
13443    "});
13444
13445    let mocked_response = lsp::SignatureHelp {
13446        signatures: vec![lsp::SignatureInformation {
13447            label: "fn sample(param1: u8, param2: u8)".to_string(),
13448            documentation: None,
13449            parameters: Some(vec![
13450                lsp::ParameterInformation {
13451                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13452                    documentation: None,
13453                },
13454                lsp::ParameterInformation {
13455                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13456                    documentation: None,
13457                },
13458            ]),
13459            active_parameter: None,
13460        }],
13461        active_signature: Some(0),
13462        active_parameter: Some(1),
13463    };
13464    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13465    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13466        .await;
13467
13468    // When selecting a range, the popover is gone.
13469    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
13470    cx.update_editor(|editor, window, cx| {
13471        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13472            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13473        })
13474    });
13475    cx.assert_editor_state(indoc! {"
13476        fn main() {
13477            sample(param1, «ˇparam2»);
13478        }
13479
13480        fn sample(param1: u8, param2: u8) {}
13481    "});
13482    cx.editor(|editor, _, _| {
13483        assert!(!editor.signature_help_state.is_shown());
13484    });
13485
13486    // When unselecting again, the popover is back if within the brackets.
13487    cx.update_editor(|editor, window, cx| {
13488        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13489            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13490        })
13491    });
13492    cx.assert_editor_state(indoc! {"
13493        fn main() {
13494            sample(param1, ˇparam2);
13495        }
13496
13497        fn sample(param1: u8, param2: u8) {}
13498    "});
13499    handle_signature_help_request(&mut cx, mocked_response).await;
13500    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13501        .await;
13502    cx.editor(|editor, _, _| {
13503        assert!(editor.signature_help_state.is_shown());
13504    });
13505
13506    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
13507    cx.update_editor(|editor, window, cx| {
13508        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13509            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
13510            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13511        })
13512    });
13513    cx.assert_editor_state(indoc! {"
13514        fn main() {
13515            sample(param1, ˇparam2);
13516        }
13517
13518        fn sample(param1: u8, param2: u8) {}
13519    "});
13520
13521    let mocked_response = lsp::SignatureHelp {
13522        signatures: vec![lsp::SignatureInformation {
13523            label: "fn sample(param1: u8, param2: u8)".to_string(),
13524            documentation: None,
13525            parameters: Some(vec![
13526                lsp::ParameterInformation {
13527                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13528                    documentation: None,
13529                },
13530                lsp::ParameterInformation {
13531                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13532                    documentation: None,
13533                },
13534            ]),
13535            active_parameter: None,
13536        }],
13537        active_signature: Some(0),
13538        active_parameter: Some(1),
13539    };
13540    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13541    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13542        .await;
13543    cx.update_editor(|editor, _, cx| {
13544        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
13545    });
13546    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13547        .await;
13548    cx.update_editor(|editor, window, cx| {
13549        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13550            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13551        })
13552    });
13553    cx.assert_editor_state(indoc! {"
13554        fn main() {
13555            sample(param1, «ˇparam2»);
13556        }
13557
13558        fn sample(param1: u8, param2: u8) {}
13559    "});
13560    cx.update_editor(|editor, window, cx| {
13561        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13562            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13563        })
13564    });
13565    cx.assert_editor_state(indoc! {"
13566        fn main() {
13567            sample(param1, ˇparam2);
13568        }
13569
13570        fn sample(param1: u8, param2: u8) {}
13571    "});
13572    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
13573        .await;
13574}
13575
13576#[gpui::test]
13577async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
13578    init_test(cx, |_| {});
13579
13580    let mut cx = EditorLspTestContext::new_rust(
13581        lsp::ServerCapabilities {
13582            signature_help_provider: Some(lsp::SignatureHelpOptions {
13583                ..Default::default()
13584            }),
13585            ..Default::default()
13586        },
13587        cx,
13588    )
13589    .await;
13590
13591    cx.set_state(indoc! {"
13592        fn main() {
13593            overloadedˇ
13594        }
13595    "});
13596
13597    cx.update_editor(|editor, window, cx| {
13598        editor.handle_input("(", window, cx);
13599        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13600    });
13601
13602    // Mock response with 3 signatures
13603    let mocked_response = lsp::SignatureHelp {
13604        signatures: vec![
13605            lsp::SignatureInformation {
13606                label: "fn overloaded(x: i32)".to_string(),
13607                documentation: None,
13608                parameters: Some(vec![lsp::ParameterInformation {
13609                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13610                    documentation: None,
13611                }]),
13612                active_parameter: None,
13613            },
13614            lsp::SignatureInformation {
13615                label: "fn overloaded(x: i32, y: i32)".to_string(),
13616                documentation: None,
13617                parameters: Some(vec![
13618                    lsp::ParameterInformation {
13619                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13620                        documentation: None,
13621                    },
13622                    lsp::ParameterInformation {
13623                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13624                        documentation: None,
13625                    },
13626                ]),
13627                active_parameter: None,
13628            },
13629            lsp::SignatureInformation {
13630                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
13631                documentation: None,
13632                parameters: Some(vec![
13633                    lsp::ParameterInformation {
13634                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13635                        documentation: None,
13636                    },
13637                    lsp::ParameterInformation {
13638                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13639                        documentation: None,
13640                    },
13641                    lsp::ParameterInformation {
13642                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
13643                        documentation: None,
13644                    },
13645                ]),
13646                active_parameter: None,
13647            },
13648        ],
13649        active_signature: Some(1),
13650        active_parameter: Some(0),
13651    };
13652    handle_signature_help_request(&mut cx, mocked_response).await;
13653
13654    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13655        .await;
13656
13657    // Verify we have multiple signatures and the right one is selected
13658    cx.editor(|editor, _, _| {
13659        let popover = editor.signature_help_state.popover().cloned().unwrap();
13660        assert_eq!(popover.signatures.len(), 3);
13661        // active_signature was 1, so that should be the current
13662        assert_eq!(popover.current_signature, 1);
13663        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
13664        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
13665        assert_eq!(
13666            popover.signatures[2].label,
13667            "fn overloaded(x: i32, y: i32, z: i32)"
13668        );
13669    });
13670
13671    // Test navigation functionality
13672    cx.update_editor(|editor, window, cx| {
13673        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13674    });
13675
13676    cx.editor(|editor, _, _| {
13677        let popover = editor.signature_help_state.popover().cloned().unwrap();
13678        assert_eq!(popover.current_signature, 2);
13679    });
13680
13681    // Test wrap around
13682    cx.update_editor(|editor, window, cx| {
13683        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13684    });
13685
13686    cx.editor(|editor, _, _| {
13687        let popover = editor.signature_help_state.popover().cloned().unwrap();
13688        assert_eq!(popover.current_signature, 0);
13689    });
13690
13691    // Test previous navigation
13692    cx.update_editor(|editor, window, cx| {
13693        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
13694    });
13695
13696    cx.editor(|editor, _, _| {
13697        let popover = editor.signature_help_state.popover().cloned().unwrap();
13698        assert_eq!(popover.current_signature, 2);
13699    });
13700}
13701
13702#[gpui::test]
13703async fn test_completion_mode(cx: &mut TestAppContext) {
13704    init_test(cx, |_| {});
13705    let mut cx = EditorLspTestContext::new_rust(
13706        lsp::ServerCapabilities {
13707            completion_provider: Some(lsp::CompletionOptions {
13708                resolve_provider: Some(true),
13709                ..Default::default()
13710            }),
13711            ..Default::default()
13712        },
13713        cx,
13714    )
13715    .await;
13716
13717    struct Run {
13718        run_description: &'static str,
13719        initial_state: String,
13720        buffer_marked_text: String,
13721        completion_label: &'static str,
13722        completion_text: &'static str,
13723        expected_with_insert_mode: String,
13724        expected_with_replace_mode: String,
13725        expected_with_replace_subsequence_mode: String,
13726        expected_with_replace_suffix_mode: String,
13727    }
13728
13729    let runs = [
13730        Run {
13731            run_description: "Start of word matches completion text",
13732            initial_state: "before ediˇ after".into(),
13733            buffer_marked_text: "before <edi|> after".into(),
13734            completion_label: "editor",
13735            completion_text: "editor",
13736            expected_with_insert_mode: "before editorˇ after".into(),
13737            expected_with_replace_mode: "before editorˇ after".into(),
13738            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13739            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13740        },
13741        Run {
13742            run_description: "Accept same text at the middle of the word",
13743            initial_state: "before ediˇtor after".into(),
13744            buffer_marked_text: "before <edi|tor> after".into(),
13745            completion_label: "editor",
13746            completion_text: "editor",
13747            expected_with_insert_mode: "before editorˇtor after".into(),
13748            expected_with_replace_mode: "before editorˇ after".into(),
13749            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13750            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13751        },
13752        Run {
13753            run_description: "End of word matches completion text -- cursor at end",
13754            initial_state: "before torˇ after".into(),
13755            buffer_marked_text: "before <tor|> after".into(),
13756            completion_label: "editor",
13757            completion_text: "editor",
13758            expected_with_insert_mode: "before editorˇ after".into(),
13759            expected_with_replace_mode: "before editorˇ after".into(),
13760            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13761            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13762        },
13763        Run {
13764            run_description: "End of word matches completion text -- cursor at start",
13765            initial_state: "before ˇtor after".into(),
13766            buffer_marked_text: "before <|tor> after".into(),
13767            completion_label: "editor",
13768            completion_text: "editor",
13769            expected_with_insert_mode: "before editorˇtor after".into(),
13770            expected_with_replace_mode: "before editorˇ after".into(),
13771            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13772            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13773        },
13774        Run {
13775            run_description: "Prepend text containing whitespace",
13776            initial_state: "pˇfield: bool".into(),
13777            buffer_marked_text: "<p|field>: bool".into(),
13778            completion_label: "pub ",
13779            completion_text: "pub ",
13780            expected_with_insert_mode: "pub ˇfield: bool".into(),
13781            expected_with_replace_mode: "pub ˇ: bool".into(),
13782            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
13783            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
13784        },
13785        Run {
13786            run_description: "Add element to start of list",
13787            initial_state: "[element_ˇelement_2]".into(),
13788            buffer_marked_text: "[<element_|element_2>]".into(),
13789            completion_label: "element_1",
13790            completion_text: "element_1",
13791            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
13792            expected_with_replace_mode: "[element_1ˇ]".into(),
13793            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
13794            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
13795        },
13796        Run {
13797            run_description: "Add element to start of list -- first and second elements are equal",
13798            initial_state: "[elˇelement]".into(),
13799            buffer_marked_text: "[<el|element>]".into(),
13800            completion_label: "element",
13801            completion_text: "element",
13802            expected_with_insert_mode: "[elementˇelement]".into(),
13803            expected_with_replace_mode: "[elementˇ]".into(),
13804            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
13805            expected_with_replace_suffix_mode: "[elementˇ]".into(),
13806        },
13807        Run {
13808            run_description: "Ends with matching suffix",
13809            initial_state: "SubˇError".into(),
13810            buffer_marked_text: "<Sub|Error>".into(),
13811            completion_label: "SubscriptionError",
13812            completion_text: "SubscriptionError",
13813            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
13814            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13815            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13816            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
13817        },
13818        Run {
13819            run_description: "Suffix is a subsequence -- contiguous",
13820            initial_state: "SubˇErr".into(),
13821            buffer_marked_text: "<Sub|Err>".into(),
13822            completion_label: "SubscriptionError",
13823            completion_text: "SubscriptionError",
13824            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
13825            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13826            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13827            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
13828        },
13829        Run {
13830            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
13831            initial_state: "Suˇscrirr".into(),
13832            buffer_marked_text: "<Su|scrirr>".into(),
13833            completion_label: "SubscriptionError",
13834            completion_text: "SubscriptionError",
13835            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
13836            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13837            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13838            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
13839        },
13840        Run {
13841            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
13842            initial_state: "foo(indˇix)".into(),
13843            buffer_marked_text: "foo(<ind|ix>)".into(),
13844            completion_label: "node_index",
13845            completion_text: "node_index",
13846            expected_with_insert_mode: "foo(node_indexˇix)".into(),
13847            expected_with_replace_mode: "foo(node_indexˇ)".into(),
13848            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
13849            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
13850        },
13851        Run {
13852            run_description: "Replace range ends before cursor - should extend to cursor",
13853            initial_state: "before editˇo after".into(),
13854            buffer_marked_text: "before <{ed}>it|o after".into(),
13855            completion_label: "editor",
13856            completion_text: "editor",
13857            expected_with_insert_mode: "before editorˇo after".into(),
13858            expected_with_replace_mode: "before editorˇo after".into(),
13859            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
13860            expected_with_replace_suffix_mode: "before editorˇo after".into(),
13861        },
13862        Run {
13863            run_description: "Uses label for suffix matching",
13864            initial_state: "before ediˇtor after".into(),
13865            buffer_marked_text: "before <edi|tor> after".into(),
13866            completion_label: "editor",
13867            completion_text: "editor()",
13868            expected_with_insert_mode: "before editor()ˇtor after".into(),
13869            expected_with_replace_mode: "before editor()ˇ after".into(),
13870            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
13871            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
13872        },
13873        Run {
13874            run_description: "Case insensitive subsequence and suffix matching",
13875            initial_state: "before EDiˇtoR after".into(),
13876            buffer_marked_text: "before <EDi|toR> after".into(),
13877            completion_label: "editor",
13878            completion_text: "editor",
13879            expected_with_insert_mode: "before editorˇtoR after".into(),
13880            expected_with_replace_mode: "before editorˇ after".into(),
13881            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13882            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13883        },
13884    ];
13885
13886    for run in runs {
13887        let run_variations = [
13888            (LspInsertMode::Insert, run.expected_with_insert_mode),
13889            (LspInsertMode::Replace, run.expected_with_replace_mode),
13890            (
13891                LspInsertMode::ReplaceSubsequence,
13892                run.expected_with_replace_subsequence_mode,
13893            ),
13894            (
13895                LspInsertMode::ReplaceSuffix,
13896                run.expected_with_replace_suffix_mode,
13897            ),
13898        ];
13899
13900        for (lsp_insert_mode, expected_text) in run_variations {
13901            eprintln!(
13902                "run = {:?}, mode = {lsp_insert_mode:.?}",
13903                run.run_description,
13904            );
13905
13906            update_test_language_settings(&mut cx, |settings| {
13907                settings.defaults.completions = Some(CompletionSettingsContent {
13908                    lsp_insert_mode: Some(lsp_insert_mode),
13909                    words: Some(WordsCompletionMode::Disabled),
13910                    words_min_length: Some(0),
13911                    ..Default::default()
13912                });
13913            });
13914
13915            cx.set_state(&run.initial_state);
13916            cx.update_editor(|editor, window, cx| {
13917                editor.show_completions(&ShowCompletions, window, cx);
13918            });
13919
13920            let counter = Arc::new(AtomicUsize::new(0));
13921            handle_completion_request_with_insert_and_replace(
13922                &mut cx,
13923                &run.buffer_marked_text,
13924                vec![(run.completion_label, run.completion_text)],
13925                counter.clone(),
13926            )
13927            .await;
13928            cx.condition(|editor, _| editor.context_menu_visible())
13929                .await;
13930            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13931
13932            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13933                editor
13934                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
13935                    .unwrap()
13936            });
13937            cx.assert_editor_state(&expected_text);
13938            handle_resolve_completion_request(&mut cx, None).await;
13939            apply_additional_edits.await.unwrap();
13940        }
13941    }
13942}
13943
13944#[gpui::test]
13945async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
13946    init_test(cx, |_| {});
13947    let mut cx = EditorLspTestContext::new_rust(
13948        lsp::ServerCapabilities {
13949            completion_provider: Some(lsp::CompletionOptions {
13950                resolve_provider: Some(true),
13951                ..Default::default()
13952            }),
13953            ..Default::default()
13954        },
13955        cx,
13956    )
13957    .await;
13958
13959    let initial_state = "SubˇError";
13960    let buffer_marked_text = "<Sub|Error>";
13961    let completion_text = "SubscriptionError";
13962    let expected_with_insert_mode = "SubscriptionErrorˇError";
13963    let expected_with_replace_mode = "SubscriptionErrorˇ";
13964
13965    update_test_language_settings(&mut cx, |settings| {
13966        settings.defaults.completions = Some(CompletionSettingsContent {
13967            words: Some(WordsCompletionMode::Disabled),
13968            words_min_length: Some(0),
13969            // set the opposite here to ensure that the action is overriding the default behavior
13970            lsp_insert_mode: Some(LspInsertMode::Insert),
13971            ..Default::default()
13972        });
13973    });
13974
13975    cx.set_state(initial_state);
13976    cx.update_editor(|editor, window, cx| {
13977        editor.show_completions(&ShowCompletions, window, cx);
13978    });
13979
13980    let counter = Arc::new(AtomicUsize::new(0));
13981    handle_completion_request_with_insert_and_replace(
13982        &mut cx,
13983        buffer_marked_text,
13984        vec![(completion_text, completion_text)],
13985        counter.clone(),
13986    )
13987    .await;
13988    cx.condition(|editor, _| editor.context_menu_visible())
13989        .await;
13990    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13991
13992    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13993        editor
13994            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13995            .unwrap()
13996    });
13997    cx.assert_editor_state(expected_with_replace_mode);
13998    handle_resolve_completion_request(&mut cx, None).await;
13999    apply_additional_edits.await.unwrap();
14000
14001    update_test_language_settings(&mut cx, |settings| {
14002        settings.defaults.completions = Some(CompletionSettingsContent {
14003            words: Some(WordsCompletionMode::Disabled),
14004            words_min_length: Some(0),
14005            // set the opposite here to ensure that the action is overriding the default behavior
14006            lsp_insert_mode: Some(LspInsertMode::Replace),
14007            ..Default::default()
14008        });
14009    });
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        buffer_marked_text,
14018        vec![(completion_text, completion_text)],
14019        counter.clone(),
14020    )
14021    .await;
14022    cx.condition(|editor, _| editor.context_menu_visible())
14023        .await;
14024    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14025
14026    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14027        editor
14028            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
14029            .unwrap()
14030    });
14031    cx.assert_editor_state(expected_with_insert_mode);
14032    handle_resolve_completion_request(&mut cx, None).await;
14033    apply_additional_edits.await.unwrap();
14034}
14035
14036#[gpui::test]
14037async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
14038    init_test(cx, |_| {});
14039    let mut cx = EditorLspTestContext::new_rust(
14040        lsp::ServerCapabilities {
14041            completion_provider: Some(lsp::CompletionOptions {
14042                resolve_provider: Some(true),
14043                ..Default::default()
14044            }),
14045            ..Default::default()
14046        },
14047        cx,
14048    )
14049    .await;
14050
14051    // scenario: surrounding text matches completion text
14052    let completion_text = "to_offset";
14053    let initial_state = indoc! {"
14054        1. buf.to_offˇsuffix
14055        2. buf.to_offˇsuf
14056        3. buf.to_offˇfix
14057        4. buf.to_offˇ
14058        5. into_offˇensive
14059        6. ˇsuffix
14060        7. let ˇ //
14061        8. aaˇzz
14062        9. buf.to_off«zzzzzˇ»suffix
14063        10. buf.«ˇzzzzz»suffix
14064        11. to_off«ˇzzzzz»
14065
14066        buf.to_offˇsuffix  // newest cursor
14067    "};
14068    let completion_marked_buffer = indoc! {"
14069        1. buf.to_offsuffix
14070        2. buf.to_offsuf
14071        3. buf.to_offfix
14072        4. buf.to_off
14073        5. into_offensive
14074        6. suffix
14075        7. let  //
14076        8. aazz
14077        9. buf.to_offzzzzzsuffix
14078        10. buf.zzzzzsuffix
14079        11. to_offzzzzz
14080
14081        buf.<to_off|suffix>  // newest cursor
14082    "};
14083    let expected = indoc! {"
14084        1. buf.to_offsetˇ
14085        2. buf.to_offsetˇsuf
14086        3. buf.to_offsetˇfix
14087        4. buf.to_offsetˇ
14088        5. into_offsetˇensive
14089        6. to_offsetˇsuffix
14090        7. let to_offsetˇ //
14091        8. aato_offsetˇzz
14092        9. buf.to_offsetˇ
14093        10. buf.to_offsetˇsuffix
14094        11. to_offsetˇ
14095
14096        buf.to_offsetˇ  // newest cursor
14097    "};
14098    cx.set_state(initial_state);
14099    cx.update_editor(|editor, window, cx| {
14100        editor.show_completions(&ShowCompletions, window, cx);
14101    });
14102    handle_completion_request_with_insert_and_replace(
14103        &mut cx,
14104        completion_marked_buffer,
14105        vec![(completion_text, completion_text)],
14106        Arc::new(AtomicUsize::new(0)),
14107    )
14108    .await;
14109    cx.condition(|editor, _| editor.context_menu_visible())
14110        .await;
14111    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14112        editor
14113            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14114            .unwrap()
14115    });
14116    cx.assert_editor_state(expected);
14117    handle_resolve_completion_request(&mut cx, None).await;
14118    apply_additional_edits.await.unwrap();
14119
14120    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
14121    let completion_text = "foo_and_bar";
14122    let initial_state = indoc! {"
14123        1. ooanbˇ
14124        2. zooanbˇ
14125        3. ooanbˇz
14126        4. zooanbˇz
14127        5. ooanˇ
14128        6. oanbˇ
14129
14130        ooanbˇ
14131    "};
14132    let completion_marked_buffer = indoc! {"
14133        1. ooanb
14134        2. zooanb
14135        3. ooanbz
14136        4. zooanbz
14137        5. ooan
14138        6. oanb
14139
14140        <ooanb|>
14141    "};
14142    let expected = indoc! {"
14143        1. foo_and_barˇ
14144        2. zfoo_and_barˇ
14145        3. foo_and_barˇz
14146        4. zfoo_and_barˇz
14147        5. ooanfoo_and_barˇ
14148        6. oanbfoo_and_barˇ
14149
14150        foo_and_barˇ
14151    "};
14152    cx.set_state(initial_state);
14153    cx.update_editor(|editor, window, cx| {
14154        editor.show_completions(&ShowCompletions, window, cx);
14155    });
14156    handle_completion_request_with_insert_and_replace(
14157        &mut cx,
14158        completion_marked_buffer,
14159        vec![(completion_text, completion_text)],
14160        Arc::new(AtomicUsize::new(0)),
14161    )
14162    .await;
14163    cx.condition(|editor, _| editor.context_menu_visible())
14164        .await;
14165    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14166        editor
14167            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14168            .unwrap()
14169    });
14170    cx.assert_editor_state(expected);
14171    handle_resolve_completion_request(&mut cx, None).await;
14172    apply_additional_edits.await.unwrap();
14173
14174    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
14175    // (expects the same as if it was inserted at the end)
14176    let completion_text = "foo_and_bar";
14177    let initial_state = indoc! {"
14178        1. ooˇanb
14179        2. zooˇanb
14180        3. ooˇanbz
14181        4. zooˇanbz
14182
14183        ooˇanb
14184    "};
14185    let completion_marked_buffer = indoc! {"
14186        1. ooanb
14187        2. zooanb
14188        3. ooanbz
14189        4. zooanbz
14190
14191        <oo|anb>
14192    "};
14193    let expected = indoc! {"
14194        1. foo_and_barˇ
14195        2. zfoo_and_barˇ
14196        3. foo_and_barˇz
14197        4. zfoo_and_barˇz
14198
14199        foo_and_barˇ
14200    "};
14201    cx.set_state(initial_state);
14202    cx.update_editor(|editor, window, cx| {
14203        editor.show_completions(&ShowCompletions, window, cx);
14204    });
14205    handle_completion_request_with_insert_and_replace(
14206        &mut cx,
14207        completion_marked_buffer,
14208        vec![(completion_text, completion_text)],
14209        Arc::new(AtomicUsize::new(0)),
14210    )
14211    .await;
14212    cx.condition(|editor, _| editor.context_menu_visible())
14213        .await;
14214    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14215        editor
14216            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14217            .unwrap()
14218    });
14219    cx.assert_editor_state(expected);
14220    handle_resolve_completion_request(&mut cx, None).await;
14221    apply_additional_edits.await.unwrap();
14222}
14223
14224// This used to crash
14225#[gpui::test]
14226async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
14227    init_test(cx, |_| {});
14228
14229    let buffer_text = indoc! {"
14230        fn main() {
14231            10.satu;
14232
14233            //
14234            // separate cursors so they open in different excerpts (manually reproducible)
14235            //
14236
14237            10.satu20;
14238        }
14239    "};
14240    let multibuffer_text_with_selections = indoc! {"
14241        fn main() {
14242            10.satuˇ;
14243
14244            //
14245
14246            //
14247
14248            10.satuˇ20;
14249        }
14250    "};
14251    let expected_multibuffer = indoc! {"
14252        fn main() {
14253            10.saturating_sub()ˇ;
14254
14255            //
14256
14257            //
14258
14259            10.saturating_sub()ˇ;
14260        }
14261    "};
14262
14263    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
14264    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
14265
14266    let fs = FakeFs::new(cx.executor());
14267    fs.insert_tree(
14268        path!("/a"),
14269        json!({
14270            "main.rs": buffer_text,
14271        }),
14272    )
14273    .await;
14274
14275    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14276    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14277    language_registry.add(rust_lang());
14278    let mut fake_servers = language_registry.register_fake_lsp(
14279        "Rust",
14280        FakeLspAdapter {
14281            capabilities: lsp::ServerCapabilities {
14282                completion_provider: Some(lsp::CompletionOptions {
14283                    resolve_provider: None,
14284                    ..lsp::CompletionOptions::default()
14285                }),
14286                ..lsp::ServerCapabilities::default()
14287            },
14288            ..FakeLspAdapter::default()
14289        },
14290    );
14291    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14292    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14293    let buffer = project
14294        .update(cx, |project, cx| {
14295            project.open_local_buffer(path!("/a/main.rs"), cx)
14296        })
14297        .await
14298        .unwrap();
14299
14300    let multi_buffer = cx.new(|cx| {
14301        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
14302        multi_buffer.push_excerpts(
14303            buffer.clone(),
14304            [ExcerptRange::new(0..first_excerpt_end)],
14305            cx,
14306        );
14307        multi_buffer.push_excerpts(
14308            buffer.clone(),
14309            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
14310            cx,
14311        );
14312        multi_buffer
14313    });
14314
14315    let editor = workspace
14316        .update(cx, |_, window, cx| {
14317            cx.new(|cx| {
14318                Editor::new(
14319                    EditorMode::Full {
14320                        scale_ui_elements_with_buffer_font_size: false,
14321                        show_active_line_background: false,
14322                        sizing_behavior: SizingBehavior::Default,
14323                    },
14324                    multi_buffer.clone(),
14325                    Some(project.clone()),
14326                    window,
14327                    cx,
14328                )
14329            })
14330        })
14331        .unwrap();
14332
14333    let pane = workspace
14334        .update(cx, |workspace, _, _| workspace.active_pane().clone())
14335        .unwrap();
14336    pane.update_in(cx, |pane, window, cx| {
14337        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
14338    });
14339
14340    let fake_server = fake_servers.next().await.unwrap();
14341
14342    editor.update_in(cx, |editor, window, cx| {
14343        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14344            s.select_ranges([
14345                Point::new(1, 11)..Point::new(1, 11),
14346                Point::new(7, 11)..Point::new(7, 11),
14347            ])
14348        });
14349
14350        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
14351    });
14352
14353    editor.update_in(cx, |editor, window, cx| {
14354        editor.show_completions(&ShowCompletions, window, cx);
14355    });
14356
14357    fake_server
14358        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14359            let completion_item = lsp::CompletionItem {
14360                label: "saturating_sub()".into(),
14361                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
14362                    lsp::InsertReplaceEdit {
14363                        new_text: "saturating_sub()".to_owned(),
14364                        insert: lsp::Range::new(
14365                            lsp::Position::new(7, 7),
14366                            lsp::Position::new(7, 11),
14367                        ),
14368                        replace: lsp::Range::new(
14369                            lsp::Position::new(7, 7),
14370                            lsp::Position::new(7, 13),
14371                        ),
14372                    },
14373                )),
14374                ..lsp::CompletionItem::default()
14375            };
14376
14377            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
14378        })
14379        .next()
14380        .await
14381        .unwrap();
14382
14383    cx.condition(&editor, |editor, _| editor.context_menu_visible())
14384        .await;
14385
14386    editor
14387        .update_in(cx, |editor, window, cx| {
14388            editor
14389                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14390                .unwrap()
14391        })
14392        .await
14393        .unwrap();
14394
14395    editor.update(cx, |editor, cx| {
14396        assert_text_with_selections(editor, expected_multibuffer, cx);
14397    })
14398}
14399
14400#[gpui::test]
14401async fn test_completion(cx: &mut TestAppContext) {
14402    init_test(cx, |_| {});
14403
14404    let mut cx = EditorLspTestContext::new_rust(
14405        lsp::ServerCapabilities {
14406            completion_provider: Some(lsp::CompletionOptions {
14407                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14408                resolve_provider: Some(true),
14409                ..Default::default()
14410            }),
14411            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14412            ..Default::default()
14413        },
14414        cx,
14415    )
14416    .await;
14417    let counter = Arc::new(AtomicUsize::new(0));
14418
14419    cx.set_state(indoc! {"
14420        oneˇ
14421        two
14422        three
14423    "});
14424    cx.simulate_keystroke(".");
14425    handle_completion_request(
14426        indoc! {"
14427            one.|<>
14428            two
14429            three
14430        "},
14431        vec!["first_completion", "second_completion"],
14432        true,
14433        counter.clone(),
14434        &mut cx,
14435    )
14436    .await;
14437    cx.condition(|editor, _| editor.context_menu_visible())
14438        .await;
14439    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14440
14441    let _handler = handle_signature_help_request(
14442        &mut cx,
14443        lsp::SignatureHelp {
14444            signatures: vec![lsp::SignatureInformation {
14445                label: "test signature".to_string(),
14446                documentation: None,
14447                parameters: Some(vec![lsp::ParameterInformation {
14448                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
14449                    documentation: None,
14450                }]),
14451                active_parameter: None,
14452            }],
14453            active_signature: None,
14454            active_parameter: None,
14455        },
14456    );
14457    cx.update_editor(|editor, window, cx| {
14458        assert!(
14459            !editor.signature_help_state.is_shown(),
14460            "No signature help was called for"
14461        );
14462        editor.show_signature_help(&ShowSignatureHelp, window, cx);
14463    });
14464    cx.run_until_parked();
14465    cx.update_editor(|editor, _, _| {
14466        assert!(
14467            !editor.signature_help_state.is_shown(),
14468            "No signature help should be shown when completions menu is open"
14469        );
14470    });
14471
14472    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14473        editor.context_menu_next(&Default::default(), window, cx);
14474        editor
14475            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14476            .unwrap()
14477    });
14478    cx.assert_editor_state(indoc! {"
14479        one.second_completionˇ
14480        two
14481        three
14482    "});
14483
14484    handle_resolve_completion_request(
14485        &mut cx,
14486        Some(vec![
14487            (
14488                //This overlaps with the primary completion edit which is
14489                //misbehavior from the LSP spec, test that we filter it out
14490                indoc! {"
14491                    one.second_ˇcompletion
14492                    two
14493                    threeˇ
14494                "},
14495                "overlapping additional edit",
14496            ),
14497            (
14498                indoc! {"
14499                    one.second_completion
14500                    two
14501                    threeˇ
14502                "},
14503                "\nadditional edit",
14504            ),
14505        ]),
14506    )
14507    .await;
14508    apply_additional_edits.await.unwrap();
14509    cx.assert_editor_state(indoc! {"
14510        one.second_completionˇ
14511        two
14512        three
14513        additional edit
14514    "});
14515
14516    cx.set_state(indoc! {"
14517        one.second_completion
14518        twoˇ
14519        threeˇ
14520        additional edit
14521    "});
14522    cx.simulate_keystroke(" ");
14523    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14524    cx.simulate_keystroke("s");
14525    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14526
14527    cx.assert_editor_state(indoc! {"
14528        one.second_completion
14529        two sˇ
14530        three sˇ
14531        additional edit
14532    "});
14533    handle_completion_request(
14534        indoc! {"
14535            one.second_completion
14536            two s
14537            three <s|>
14538            additional edit
14539        "},
14540        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14541        true,
14542        counter.clone(),
14543        &mut cx,
14544    )
14545    .await;
14546    cx.condition(|editor, _| editor.context_menu_visible())
14547        .await;
14548    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14549
14550    cx.simulate_keystroke("i");
14551
14552    handle_completion_request(
14553        indoc! {"
14554            one.second_completion
14555            two si
14556            three <si|>
14557            additional edit
14558        "},
14559        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14560        true,
14561        counter.clone(),
14562        &mut cx,
14563    )
14564    .await;
14565    cx.condition(|editor, _| editor.context_menu_visible())
14566        .await;
14567    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14568
14569    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14570        editor
14571            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14572            .unwrap()
14573    });
14574    cx.assert_editor_state(indoc! {"
14575        one.second_completion
14576        two sixth_completionˇ
14577        three sixth_completionˇ
14578        additional edit
14579    "});
14580
14581    apply_additional_edits.await.unwrap();
14582
14583    update_test_language_settings(&mut cx, |settings| {
14584        settings.defaults.show_completions_on_input = Some(false);
14585    });
14586    cx.set_state("editorˇ");
14587    cx.simulate_keystroke(".");
14588    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14589    cx.simulate_keystrokes("c l o");
14590    cx.assert_editor_state("editor.cloˇ");
14591    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14592    cx.update_editor(|editor, window, cx| {
14593        editor.show_completions(&ShowCompletions, window, cx);
14594    });
14595    handle_completion_request(
14596        "editor.<clo|>",
14597        vec!["close", "clobber"],
14598        true,
14599        counter.clone(),
14600        &mut cx,
14601    )
14602    .await;
14603    cx.condition(|editor, _| editor.context_menu_visible())
14604        .await;
14605    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
14606
14607    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14608        editor
14609            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14610            .unwrap()
14611    });
14612    cx.assert_editor_state("editor.clobberˇ");
14613    handle_resolve_completion_request(&mut cx, None).await;
14614    apply_additional_edits.await.unwrap();
14615}
14616
14617#[gpui::test]
14618async fn test_completion_reuse(cx: &mut TestAppContext) {
14619    init_test(cx, |_| {});
14620
14621    let mut cx = EditorLspTestContext::new_rust(
14622        lsp::ServerCapabilities {
14623            completion_provider: Some(lsp::CompletionOptions {
14624                trigger_characters: Some(vec![".".to_string()]),
14625                ..Default::default()
14626            }),
14627            ..Default::default()
14628        },
14629        cx,
14630    )
14631    .await;
14632
14633    let counter = Arc::new(AtomicUsize::new(0));
14634    cx.set_state("objˇ");
14635    cx.simulate_keystroke(".");
14636
14637    // Initial completion request returns complete results
14638    let is_incomplete = false;
14639    handle_completion_request(
14640        "obj.|<>",
14641        vec!["a", "ab", "abc"],
14642        is_incomplete,
14643        counter.clone(),
14644        &mut cx,
14645    )
14646    .await;
14647    cx.run_until_parked();
14648    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14649    cx.assert_editor_state("obj.ˇ");
14650    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14651
14652    // Type "a" - filters existing completions
14653    cx.simulate_keystroke("a");
14654    cx.run_until_parked();
14655    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14656    cx.assert_editor_state("obj.aˇ");
14657    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14658
14659    // Type "b" - filters existing completions
14660    cx.simulate_keystroke("b");
14661    cx.run_until_parked();
14662    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14663    cx.assert_editor_state("obj.abˇ");
14664    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14665
14666    // Type "c" - filters existing completions
14667    cx.simulate_keystroke("c");
14668    cx.run_until_parked();
14669    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14670    cx.assert_editor_state("obj.abcˇ");
14671    check_displayed_completions(vec!["abc"], &mut cx);
14672
14673    // Backspace to delete "c" - filters existing completions
14674    cx.update_editor(|editor, window, cx| {
14675        editor.backspace(&Backspace, window, cx);
14676    });
14677    cx.run_until_parked();
14678    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14679    cx.assert_editor_state("obj.abˇ");
14680    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14681
14682    // Moving cursor to the left dismisses menu.
14683    cx.update_editor(|editor, window, cx| {
14684        editor.move_left(&MoveLeft, window, cx);
14685    });
14686    cx.run_until_parked();
14687    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14688    cx.assert_editor_state("obj.aˇb");
14689    cx.update_editor(|editor, _, _| {
14690        assert_eq!(editor.context_menu_visible(), false);
14691    });
14692
14693    // Type "b" - new request
14694    cx.simulate_keystroke("b");
14695    let is_incomplete = false;
14696    handle_completion_request(
14697        "obj.<ab|>a",
14698        vec!["ab", "abc"],
14699        is_incomplete,
14700        counter.clone(),
14701        &mut cx,
14702    )
14703    .await;
14704    cx.run_until_parked();
14705    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14706    cx.assert_editor_state("obj.abˇb");
14707    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14708
14709    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
14710    cx.update_editor(|editor, window, cx| {
14711        editor.backspace(&Backspace, window, cx);
14712    });
14713    let is_incomplete = false;
14714    handle_completion_request(
14715        "obj.<a|>b",
14716        vec!["a", "ab", "abc"],
14717        is_incomplete,
14718        counter.clone(),
14719        &mut cx,
14720    )
14721    .await;
14722    cx.run_until_parked();
14723    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14724    cx.assert_editor_state("obj.aˇb");
14725    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14726
14727    // Backspace to delete "a" - dismisses menu.
14728    cx.update_editor(|editor, window, cx| {
14729        editor.backspace(&Backspace, window, cx);
14730    });
14731    cx.run_until_parked();
14732    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14733    cx.assert_editor_state("obj.ˇb");
14734    cx.update_editor(|editor, _, _| {
14735        assert_eq!(editor.context_menu_visible(), false);
14736    });
14737}
14738
14739#[gpui::test]
14740async fn test_word_completion(cx: &mut TestAppContext) {
14741    let lsp_fetch_timeout_ms = 10;
14742    init_test(cx, |language_settings| {
14743        language_settings.defaults.completions = Some(CompletionSettingsContent {
14744            words_min_length: Some(0),
14745            lsp_fetch_timeout_ms: Some(10),
14746            lsp_insert_mode: Some(LspInsertMode::Insert),
14747            ..Default::default()
14748        });
14749    });
14750
14751    let mut cx = EditorLspTestContext::new_rust(
14752        lsp::ServerCapabilities {
14753            completion_provider: Some(lsp::CompletionOptions {
14754                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14755                ..lsp::CompletionOptions::default()
14756            }),
14757            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14758            ..lsp::ServerCapabilities::default()
14759        },
14760        cx,
14761    )
14762    .await;
14763
14764    let throttle_completions = Arc::new(AtomicBool::new(false));
14765
14766    let lsp_throttle_completions = throttle_completions.clone();
14767    let _completion_requests_handler =
14768        cx.lsp
14769            .server
14770            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
14771                let lsp_throttle_completions = lsp_throttle_completions.clone();
14772                let cx = cx.clone();
14773                async move {
14774                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
14775                        cx.background_executor()
14776                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
14777                            .await;
14778                    }
14779                    Ok(Some(lsp::CompletionResponse::Array(vec![
14780                        lsp::CompletionItem {
14781                            label: "first".into(),
14782                            ..lsp::CompletionItem::default()
14783                        },
14784                        lsp::CompletionItem {
14785                            label: "last".into(),
14786                            ..lsp::CompletionItem::default()
14787                        },
14788                    ])))
14789                }
14790            });
14791
14792    cx.set_state(indoc! {"
14793        oneˇ
14794        two
14795        three
14796    "});
14797    cx.simulate_keystroke(".");
14798    cx.executor().run_until_parked();
14799    cx.condition(|editor, _| editor.context_menu_visible())
14800        .await;
14801    cx.update_editor(|editor, window, cx| {
14802        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14803        {
14804            assert_eq!(
14805                completion_menu_entries(menu),
14806                &["first", "last"],
14807                "When LSP server is fast to reply, no fallback word completions are used"
14808            );
14809        } else {
14810            panic!("expected completion menu to be open");
14811        }
14812        editor.cancel(&Cancel, window, cx);
14813    });
14814    cx.executor().run_until_parked();
14815    cx.condition(|editor, _| !editor.context_menu_visible())
14816        .await;
14817
14818    throttle_completions.store(true, atomic::Ordering::Release);
14819    cx.simulate_keystroke(".");
14820    cx.executor()
14821        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
14822    cx.executor().run_until_parked();
14823    cx.condition(|editor, _| editor.context_menu_visible())
14824        .await;
14825    cx.update_editor(|editor, _, _| {
14826        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14827        {
14828            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
14829                "When LSP server is slow, document words can be shown instead, if configured accordingly");
14830        } else {
14831            panic!("expected completion menu to be open");
14832        }
14833    });
14834}
14835
14836#[gpui::test]
14837async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
14838    init_test(cx, |language_settings| {
14839        language_settings.defaults.completions = Some(CompletionSettingsContent {
14840            words: Some(WordsCompletionMode::Enabled),
14841            words_min_length: Some(0),
14842            lsp_insert_mode: Some(LspInsertMode::Insert),
14843            ..Default::default()
14844        });
14845    });
14846
14847    let mut cx = EditorLspTestContext::new_rust(
14848        lsp::ServerCapabilities {
14849            completion_provider: Some(lsp::CompletionOptions {
14850                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14851                ..lsp::CompletionOptions::default()
14852            }),
14853            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14854            ..lsp::ServerCapabilities::default()
14855        },
14856        cx,
14857    )
14858    .await;
14859
14860    let _completion_requests_handler =
14861        cx.lsp
14862            .server
14863            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14864                Ok(Some(lsp::CompletionResponse::Array(vec![
14865                    lsp::CompletionItem {
14866                        label: "first".into(),
14867                        ..lsp::CompletionItem::default()
14868                    },
14869                    lsp::CompletionItem {
14870                        label: "last".into(),
14871                        ..lsp::CompletionItem::default()
14872                    },
14873                ])))
14874            });
14875
14876    cx.set_state(indoc! {"ˇ
14877        first
14878        last
14879        second
14880    "});
14881    cx.simulate_keystroke(".");
14882    cx.executor().run_until_parked();
14883    cx.condition(|editor, _| editor.context_menu_visible())
14884        .await;
14885    cx.update_editor(|editor, _, _| {
14886        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14887        {
14888            assert_eq!(
14889                completion_menu_entries(menu),
14890                &["first", "last", "second"],
14891                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
14892            );
14893        } else {
14894            panic!("expected completion menu to be open");
14895        }
14896    });
14897}
14898
14899#[gpui::test]
14900async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
14901    init_test(cx, |language_settings| {
14902        language_settings.defaults.completions = Some(CompletionSettingsContent {
14903            words: Some(WordsCompletionMode::Disabled),
14904            words_min_length: Some(0),
14905            lsp_insert_mode: Some(LspInsertMode::Insert),
14906            ..Default::default()
14907        });
14908    });
14909
14910    let mut cx = EditorLspTestContext::new_rust(
14911        lsp::ServerCapabilities {
14912            completion_provider: Some(lsp::CompletionOptions {
14913                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14914                ..lsp::CompletionOptions::default()
14915            }),
14916            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14917            ..lsp::ServerCapabilities::default()
14918        },
14919        cx,
14920    )
14921    .await;
14922
14923    let _completion_requests_handler =
14924        cx.lsp
14925            .server
14926            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14927                panic!("LSP completions should not be queried when dealing with word completions")
14928            });
14929
14930    cx.set_state(indoc! {"ˇ
14931        first
14932        last
14933        second
14934    "});
14935    cx.update_editor(|editor, window, cx| {
14936        editor.show_word_completions(&ShowWordCompletions, window, cx);
14937    });
14938    cx.executor().run_until_parked();
14939    cx.condition(|editor, _| editor.context_menu_visible())
14940        .await;
14941    cx.update_editor(|editor, _, _| {
14942        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14943        {
14944            assert_eq!(
14945                completion_menu_entries(menu),
14946                &["first", "last", "second"],
14947                "`ShowWordCompletions` action should show word completions"
14948            );
14949        } else {
14950            panic!("expected completion menu to be open");
14951        }
14952    });
14953
14954    cx.simulate_keystroke("l");
14955    cx.executor().run_until_parked();
14956    cx.condition(|editor, _| editor.context_menu_visible())
14957        .await;
14958    cx.update_editor(|editor, _, _| {
14959        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14960        {
14961            assert_eq!(
14962                completion_menu_entries(menu),
14963                &["last"],
14964                "After showing word completions, further editing should filter them and not query the LSP"
14965            );
14966        } else {
14967            panic!("expected completion menu to be open");
14968        }
14969    });
14970}
14971
14972#[gpui::test]
14973async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
14974    init_test(cx, |language_settings| {
14975        language_settings.defaults.completions = Some(CompletionSettingsContent {
14976            words_min_length: Some(0),
14977            lsp: Some(false),
14978            lsp_insert_mode: Some(LspInsertMode::Insert),
14979            ..Default::default()
14980        });
14981    });
14982
14983    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14984
14985    cx.set_state(indoc! {"ˇ
14986        0_usize
14987        let
14988        33
14989        4.5f32
14990    "});
14991    cx.update_editor(|editor, window, cx| {
14992        editor.show_completions(&ShowCompletions, window, cx);
14993    });
14994    cx.executor().run_until_parked();
14995    cx.condition(|editor, _| editor.context_menu_visible())
14996        .await;
14997    cx.update_editor(|editor, window, cx| {
14998        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14999        {
15000            assert_eq!(
15001                completion_menu_entries(menu),
15002                &["let"],
15003                "With no digits in the completion query, no digits should be in the word completions"
15004            );
15005        } else {
15006            panic!("expected completion menu to be open");
15007        }
15008        editor.cancel(&Cancel, window, cx);
15009    });
15010
15011    cx.set_state(indoc! {"15012        0_usize
15013        let
15014        3
15015        33.35f32
15016    "});
15017    cx.update_editor(|editor, window, cx| {
15018        editor.show_completions(&ShowCompletions, window, cx);
15019    });
15020    cx.executor().run_until_parked();
15021    cx.condition(|editor, _| editor.context_menu_visible())
15022        .await;
15023    cx.update_editor(|editor, _, _| {
15024        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15025        {
15026            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
15027                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
15028        } else {
15029            panic!("expected completion menu to be open");
15030        }
15031    });
15032}
15033
15034#[gpui::test]
15035async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
15036    init_test(cx, |language_settings| {
15037        language_settings.defaults.completions = Some(CompletionSettingsContent {
15038            words: Some(WordsCompletionMode::Enabled),
15039            words_min_length: Some(3),
15040            lsp_insert_mode: Some(LspInsertMode::Insert),
15041            ..Default::default()
15042        });
15043    });
15044
15045    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15046    cx.set_state(indoc! {"ˇ
15047        wow
15048        wowen
15049        wowser
15050    "});
15051    cx.simulate_keystroke("w");
15052    cx.executor().run_until_parked();
15053    cx.update_editor(|editor, _, _| {
15054        if editor.context_menu.borrow_mut().is_some() {
15055            panic!(
15056                "expected completion menu to be hidden, as words completion threshold is not met"
15057            );
15058        }
15059    });
15060
15061    cx.update_editor(|editor, window, cx| {
15062        editor.show_word_completions(&ShowWordCompletions, window, cx);
15063    });
15064    cx.executor().run_until_parked();
15065    cx.update_editor(|editor, window, cx| {
15066        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15067        {
15068            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");
15069        } else {
15070            panic!("expected completion menu to be open after the word completions are called with an action");
15071        }
15072
15073        editor.cancel(&Cancel, window, cx);
15074    });
15075    cx.update_editor(|editor, _, _| {
15076        if editor.context_menu.borrow_mut().is_some() {
15077            panic!("expected completion menu to be hidden after canceling");
15078        }
15079    });
15080
15081    cx.simulate_keystroke("o");
15082    cx.executor().run_until_parked();
15083    cx.update_editor(|editor, _, _| {
15084        if editor.context_menu.borrow_mut().is_some() {
15085            panic!(
15086                "expected completion menu to be hidden, as words completion threshold is not met still"
15087            );
15088        }
15089    });
15090
15091    cx.simulate_keystroke("w");
15092    cx.executor().run_until_parked();
15093    cx.update_editor(|editor, _, _| {
15094        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15095        {
15096            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
15097        } else {
15098            panic!("expected completion menu to be open after the word completions threshold is met");
15099        }
15100    });
15101}
15102
15103#[gpui::test]
15104async fn test_word_completions_disabled(cx: &mut TestAppContext) {
15105    init_test(cx, |language_settings| {
15106        language_settings.defaults.completions = Some(CompletionSettingsContent {
15107            words: Some(WordsCompletionMode::Enabled),
15108            words_min_length: Some(0),
15109            lsp_insert_mode: Some(LspInsertMode::Insert),
15110            ..Default::default()
15111        });
15112    });
15113
15114    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15115    cx.update_editor(|editor, _, _| {
15116        editor.disable_word_completions();
15117    });
15118    cx.set_state(indoc! {"ˇ
15119        wow
15120        wowen
15121        wowser
15122    "});
15123    cx.simulate_keystroke("w");
15124    cx.executor().run_until_parked();
15125    cx.update_editor(|editor, _, _| {
15126        if editor.context_menu.borrow_mut().is_some() {
15127            panic!(
15128                "expected completion menu to be hidden, as words completion are disabled for this editor"
15129            );
15130        }
15131    });
15132
15133    cx.update_editor(|editor, window, cx| {
15134        editor.show_word_completions(&ShowWordCompletions, window, cx);
15135    });
15136    cx.executor().run_until_parked();
15137    cx.update_editor(|editor, _, _| {
15138        if editor.context_menu.borrow_mut().is_some() {
15139            panic!(
15140                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
15141            );
15142        }
15143    });
15144}
15145
15146#[gpui::test]
15147async fn test_word_completions_disabled_with_no_provider(cx: &mut TestAppContext) {
15148    init_test(cx, |language_settings| {
15149        language_settings.defaults.completions = Some(CompletionSettingsContent {
15150            words: Some(WordsCompletionMode::Disabled),
15151            words_min_length: Some(0),
15152            lsp_insert_mode: Some(LspInsertMode::Insert),
15153            ..Default::default()
15154        });
15155    });
15156
15157    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15158    cx.update_editor(|editor, _, _| {
15159        editor.set_completion_provider(None);
15160    });
15161    cx.set_state(indoc! {"ˇ
15162        wow
15163        wowen
15164        wowser
15165    "});
15166    cx.simulate_keystroke("w");
15167    cx.executor().run_until_parked();
15168    cx.update_editor(|editor, _, _| {
15169        if editor.context_menu.borrow_mut().is_some() {
15170            panic!("expected completion menu to be hidden, as disabled in settings");
15171        }
15172    });
15173}
15174
15175fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
15176    let position = || lsp::Position {
15177        line: params.text_document_position.position.line,
15178        character: params.text_document_position.position.character,
15179    };
15180    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15181        range: lsp::Range {
15182            start: position(),
15183            end: position(),
15184        },
15185        new_text: text.to_string(),
15186    }))
15187}
15188
15189#[gpui::test]
15190async fn test_multiline_completion(cx: &mut TestAppContext) {
15191    init_test(cx, |_| {});
15192
15193    let fs = FakeFs::new(cx.executor());
15194    fs.insert_tree(
15195        path!("/a"),
15196        json!({
15197            "main.ts": "a",
15198        }),
15199    )
15200    .await;
15201
15202    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15203    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15204    let typescript_language = Arc::new(Language::new(
15205        LanguageConfig {
15206            name: "TypeScript".into(),
15207            matcher: LanguageMatcher {
15208                path_suffixes: vec!["ts".to_string()],
15209                ..LanguageMatcher::default()
15210            },
15211            line_comments: vec!["// ".into()],
15212            ..LanguageConfig::default()
15213        },
15214        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
15215    ));
15216    language_registry.add(typescript_language.clone());
15217    let mut fake_servers = language_registry.register_fake_lsp(
15218        "TypeScript",
15219        FakeLspAdapter {
15220            capabilities: lsp::ServerCapabilities {
15221                completion_provider: Some(lsp::CompletionOptions {
15222                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15223                    ..lsp::CompletionOptions::default()
15224                }),
15225                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15226                ..lsp::ServerCapabilities::default()
15227            },
15228            // Emulate vtsls label generation
15229            label_for_completion: Some(Box::new(|item, _| {
15230                let text = if let Some(description) = item
15231                    .label_details
15232                    .as_ref()
15233                    .and_then(|label_details| label_details.description.as_ref())
15234                {
15235                    format!("{} {}", item.label, description)
15236                } else if let Some(detail) = &item.detail {
15237                    format!("{} {}", item.label, detail)
15238                } else {
15239                    item.label.clone()
15240                };
15241                Some(language::CodeLabel::plain(text, None))
15242            })),
15243            ..FakeLspAdapter::default()
15244        },
15245    );
15246    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15247    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15248    let worktree_id = workspace
15249        .update(cx, |workspace, _window, cx| {
15250            workspace.project().update(cx, |project, cx| {
15251                project.worktrees(cx).next().unwrap().read(cx).id()
15252            })
15253        })
15254        .unwrap();
15255    let _buffer = project
15256        .update(cx, |project, cx| {
15257            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
15258        })
15259        .await
15260        .unwrap();
15261    let editor = workspace
15262        .update(cx, |workspace, window, cx| {
15263            workspace.open_path((worktree_id, rel_path("main.ts")), None, true, window, cx)
15264        })
15265        .unwrap()
15266        .await
15267        .unwrap()
15268        .downcast::<Editor>()
15269        .unwrap();
15270    let fake_server = fake_servers.next().await.unwrap();
15271
15272    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
15273    let multiline_label_2 = "a\nb\nc\n";
15274    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
15275    let multiline_description = "d\ne\nf\n";
15276    let multiline_detail_2 = "g\nh\ni\n";
15277
15278    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
15279        move |params, _| async move {
15280            Ok(Some(lsp::CompletionResponse::Array(vec![
15281                lsp::CompletionItem {
15282                    label: multiline_label.to_string(),
15283                    text_edit: gen_text_edit(&params, "new_text_1"),
15284                    ..lsp::CompletionItem::default()
15285                },
15286                lsp::CompletionItem {
15287                    label: "single line label 1".to_string(),
15288                    detail: Some(multiline_detail.to_string()),
15289                    text_edit: gen_text_edit(&params, "new_text_2"),
15290                    ..lsp::CompletionItem::default()
15291                },
15292                lsp::CompletionItem {
15293                    label: "single line label 2".to_string(),
15294                    label_details: Some(lsp::CompletionItemLabelDetails {
15295                        description: Some(multiline_description.to_string()),
15296                        detail: None,
15297                    }),
15298                    text_edit: gen_text_edit(&params, "new_text_2"),
15299                    ..lsp::CompletionItem::default()
15300                },
15301                lsp::CompletionItem {
15302                    label: multiline_label_2.to_string(),
15303                    detail: Some(multiline_detail_2.to_string()),
15304                    text_edit: gen_text_edit(&params, "new_text_3"),
15305                    ..lsp::CompletionItem::default()
15306                },
15307                lsp::CompletionItem {
15308                    label: "Label with many     spaces and \t but without newlines".to_string(),
15309                    detail: Some(
15310                        "Details with many     spaces and \t but without newlines".to_string(),
15311                    ),
15312                    text_edit: gen_text_edit(&params, "new_text_4"),
15313                    ..lsp::CompletionItem::default()
15314                },
15315            ])))
15316        },
15317    );
15318
15319    editor.update_in(cx, |editor, window, cx| {
15320        cx.focus_self(window);
15321        editor.move_to_end(&MoveToEnd, window, cx);
15322        editor.handle_input(".", window, cx);
15323    });
15324    cx.run_until_parked();
15325    completion_handle.next().await.unwrap();
15326
15327    editor.update(cx, |editor, _| {
15328        assert!(editor.context_menu_visible());
15329        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15330        {
15331            let completion_labels = menu
15332                .completions
15333                .borrow()
15334                .iter()
15335                .map(|c| c.label.text.clone())
15336                .collect::<Vec<_>>();
15337            assert_eq!(
15338                completion_labels,
15339                &[
15340                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
15341                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
15342                    "single line label 2 d e f ",
15343                    "a b c g h i ",
15344                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
15345                ],
15346                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
15347            );
15348
15349            for completion in menu
15350                .completions
15351                .borrow()
15352                .iter() {
15353                    assert_eq!(
15354                        completion.label.filter_range,
15355                        0..completion.label.text.len(),
15356                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
15357                    );
15358                }
15359        } else {
15360            panic!("expected completion menu to be open");
15361        }
15362    });
15363}
15364
15365#[gpui::test]
15366async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
15367    init_test(cx, |_| {});
15368    let mut cx = EditorLspTestContext::new_rust(
15369        lsp::ServerCapabilities {
15370            completion_provider: Some(lsp::CompletionOptions {
15371                trigger_characters: Some(vec![".".to_string()]),
15372                ..Default::default()
15373            }),
15374            ..Default::default()
15375        },
15376        cx,
15377    )
15378    .await;
15379    cx.lsp
15380        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15381            Ok(Some(lsp::CompletionResponse::Array(vec![
15382                lsp::CompletionItem {
15383                    label: "first".into(),
15384                    ..Default::default()
15385                },
15386                lsp::CompletionItem {
15387                    label: "last".into(),
15388                    ..Default::default()
15389                },
15390            ])))
15391        });
15392    cx.set_state("variableˇ");
15393    cx.simulate_keystroke(".");
15394    cx.executor().run_until_parked();
15395
15396    cx.update_editor(|editor, _, _| {
15397        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15398        {
15399            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
15400        } else {
15401            panic!("expected completion menu to be open");
15402        }
15403    });
15404
15405    cx.update_editor(|editor, window, cx| {
15406        editor.move_page_down(&MovePageDown::default(), window, cx);
15407        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15408        {
15409            assert!(
15410                menu.selected_item == 1,
15411                "expected PageDown to select the last item from the context menu"
15412            );
15413        } else {
15414            panic!("expected completion menu to stay open after PageDown");
15415        }
15416    });
15417
15418    cx.update_editor(|editor, window, cx| {
15419        editor.move_page_up(&MovePageUp::default(), window, cx);
15420        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15421        {
15422            assert!(
15423                menu.selected_item == 0,
15424                "expected PageUp to select the first item from the context menu"
15425            );
15426        } else {
15427            panic!("expected completion menu to stay open after PageUp");
15428        }
15429    });
15430}
15431
15432#[gpui::test]
15433async fn test_as_is_completions(cx: &mut TestAppContext) {
15434    init_test(cx, |_| {});
15435    let mut cx = EditorLspTestContext::new_rust(
15436        lsp::ServerCapabilities {
15437            completion_provider: Some(lsp::CompletionOptions {
15438                ..Default::default()
15439            }),
15440            ..Default::default()
15441        },
15442        cx,
15443    )
15444    .await;
15445    cx.lsp
15446        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15447            Ok(Some(lsp::CompletionResponse::Array(vec![
15448                lsp::CompletionItem {
15449                    label: "unsafe".into(),
15450                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15451                        range: lsp::Range {
15452                            start: lsp::Position {
15453                                line: 1,
15454                                character: 2,
15455                            },
15456                            end: lsp::Position {
15457                                line: 1,
15458                                character: 3,
15459                            },
15460                        },
15461                        new_text: "unsafe".to_string(),
15462                    })),
15463                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
15464                    ..Default::default()
15465                },
15466            ])))
15467        });
15468    cx.set_state("fn a() {}\n");
15469    cx.executor().run_until_parked();
15470    cx.update_editor(|editor, window, cx| {
15471        editor.trigger_completion_on_input("n", true, window, cx)
15472    });
15473    cx.executor().run_until_parked();
15474
15475    cx.update_editor(|editor, window, cx| {
15476        editor.confirm_completion(&Default::default(), window, cx)
15477    });
15478    cx.executor().run_until_parked();
15479    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
15480}
15481
15482#[gpui::test]
15483async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
15484    init_test(cx, |_| {});
15485    let language =
15486        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
15487    let mut cx = EditorLspTestContext::new(
15488        language,
15489        lsp::ServerCapabilities {
15490            completion_provider: Some(lsp::CompletionOptions {
15491                ..lsp::CompletionOptions::default()
15492            }),
15493            ..lsp::ServerCapabilities::default()
15494        },
15495        cx,
15496    )
15497    .await;
15498
15499    cx.set_state(
15500        "#ifndef BAR_H
15501#define BAR_H
15502
15503#include <stdbool.h>
15504
15505int fn_branch(bool do_branch1, bool do_branch2);
15506
15507#endif // BAR_H
15508ˇ",
15509    );
15510    cx.executor().run_until_parked();
15511    cx.update_editor(|editor, window, cx| {
15512        editor.handle_input("#", window, cx);
15513    });
15514    cx.executor().run_until_parked();
15515    cx.update_editor(|editor, window, cx| {
15516        editor.handle_input("i", window, cx);
15517    });
15518    cx.executor().run_until_parked();
15519    cx.update_editor(|editor, window, cx| {
15520        editor.handle_input("n", window, cx);
15521    });
15522    cx.executor().run_until_parked();
15523    cx.assert_editor_state(
15524        "#ifndef BAR_H
15525#define BAR_H
15526
15527#include <stdbool.h>
15528
15529int fn_branch(bool do_branch1, bool do_branch2);
15530
15531#endif // BAR_H
15532#inˇ",
15533    );
15534
15535    cx.lsp
15536        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15537            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15538                is_incomplete: false,
15539                item_defaults: None,
15540                items: vec![lsp::CompletionItem {
15541                    kind: Some(lsp::CompletionItemKind::SNIPPET),
15542                    label_details: Some(lsp::CompletionItemLabelDetails {
15543                        detail: Some("header".to_string()),
15544                        description: None,
15545                    }),
15546                    label: " include".to_string(),
15547                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15548                        range: lsp::Range {
15549                            start: lsp::Position {
15550                                line: 8,
15551                                character: 1,
15552                            },
15553                            end: lsp::Position {
15554                                line: 8,
15555                                character: 1,
15556                            },
15557                        },
15558                        new_text: "include \"$0\"".to_string(),
15559                    })),
15560                    sort_text: Some("40b67681include".to_string()),
15561                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15562                    filter_text: Some("include".to_string()),
15563                    insert_text: Some("include \"$0\"".to_string()),
15564                    ..lsp::CompletionItem::default()
15565                }],
15566            })))
15567        });
15568    cx.update_editor(|editor, window, cx| {
15569        editor.show_completions(&ShowCompletions, window, cx);
15570    });
15571    cx.executor().run_until_parked();
15572    cx.update_editor(|editor, window, cx| {
15573        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15574    });
15575    cx.executor().run_until_parked();
15576    cx.assert_editor_state(
15577        "#ifndef BAR_H
15578#define BAR_H
15579
15580#include <stdbool.h>
15581
15582int fn_branch(bool do_branch1, bool do_branch2);
15583
15584#endif // BAR_H
15585#include \"ˇ\"",
15586    );
15587
15588    cx.lsp
15589        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15590            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15591                is_incomplete: true,
15592                item_defaults: None,
15593                items: vec![lsp::CompletionItem {
15594                    kind: Some(lsp::CompletionItemKind::FILE),
15595                    label: "AGL/".to_string(),
15596                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15597                        range: lsp::Range {
15598                            start: lsp::Position {
15599                                line: 8,
15600                                character: 10,
15601                            },
15602                            end: lsp::Position {
15603                                line: 8,
15604                                character: 11,
15605                            },
15606                        },
15607                        new_text: "AGL/".to_string(),
15608                    })),
15609                    sort_text: Some("40b67681AGL/".to_string()),
15610                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15611                    filter_text: Some("AGL/".to_string()),
15612                    insert_text: Some("AGL/".to_string()),
15613                    ..lsp::CompletionItem::default()
15614                }],
15615            })))
15616        });
15617    cx.update_editor(|editor, window, cx| {
15618        editor.show_completions(&ShowCompletions, window, cx);
15619    });
15620    cx.executor().run_until_parked();
15621    cx.update_editor(|editor, window, cx| {
15622        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15623    });
15624    cx.executor().run_until_parked();
15625    cx.assert_editor_state(
15626        r##"#ifndef BAR_H
15627#define BAR_H
15628
15629#include <stdbool.h>
15630
15631int fn_branch(bool do_branch1, bool do_branch2);
15632
15633#endif // BAR_H
15634#include "AGL/ˇ"##,
15635    );
15636
15637    cx.update_editor(|editor, window, cx| {
15638        editor.handle_input("\"", window, cx);
15639    });
15640    cx.executor().run_until_parked();
15641    cx.assert_editor_state(
15642        r##"#ifndef BAR_H
15643#define BAR_H
15644
15645#include <stdbool.h>
15646
15647int fn_branch(bool do_branch1, bool do_branch2);
15648
15649#endif // BAR_H
15650#include "AGL/"ˇ"##,
15651    );
15652}
15653
15654#[gpui::test]
15655async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
15656    init_test(cx, |_| {});
15657
15658    let mut cx = EditorLspTestContext::new_rust(
15659        lsp::ServerCapabilities {
15660            completion_provider: Some(lsp::CompletionOptions {
15661                trigger_characters: Some(vec![".".to_string()]),
15662                resolve_provider: Some(true),
15663                ..Default::default()
15664            }),
15665            ..Default::default()
15666        },
15667        cx,
15668    )
15669    .await;
15670
15671    cx.set_state("fn main() { let a = 2ˇ; }");
15672    cx.simulate_keystroke(".");
15673    let completion_item = lsp::CompletionItem {
15674        label: "Some".into(),
15675        kind: Some(lsp::CompletionItemKind::SNIPPET),
15676        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15677        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15678            kind: lsp::MarkupKind::Markdown,
15679            value: "```rust\nSome(2)\n```".to_string(),
15680        })),
15681        deprecated: Some(false),
15682        sort_text: Some("Some".to_string()),
15683        filter_text: Some("Some".to_string()),
15684        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15685        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15686            range: lsp::Range {
15687                start: lsp::Position {
15688                    line: 0,
15689                    character: 22,
15690                },
15691                end: lsp::Position {
15692                    line: 0,
15693                    character: 22,
15694                },
15695            },
15696            new_text: "Some(2)".to_string(),
15697        })),
15698        additional_text_edits: Some(vec![lsp::TextEdit {
15699            range: lsp::Range {
15700                start: lsp::Position {
15701                    line: 0,
15702                    character: 20,
15703                },
15704                end: lsp::Position {
15705                    line: 0,
15706                    character: 22,
15707                },
15708            },
15709            new_text: "".to_string(),
15710        }]),
15711        ..Default::default()
15712    };
15713
15714    let closure_completion_item = completion_item.clone();
15715    let counter = Arc::new(AtomicUsize::new(0));
15716    let counter_clone = counter.clone();
15717    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15718        let task_completion_item = closure_completion_item.clone();
15719        counter_clone.fetch_add(1, atomic::Ordering::Release);
15720        async move {
15721            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15722                is_incomplete: true,
15723                item_defaults: None,
15724                items: vec![task_completion_item],
15725            })))
15726        }
15727    });
15728
15729    cx.condition(|editor, _| editor.context_menu_visible())
15730        .await;
15731    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
15732    assert!(request.next().await.is_some());
15733    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15734
15735    cx.simulate_keystrokes("S o m");
15736    cx.condition(|editor, _| editor.context_menu_visible())
15737        .await;
15738    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
15739    assert!(request.next().await.is_some());
15740    assert!(request.next().await.is_some());
15741    assert!(request.next().await.is_some());
15742    request.close();
15743    assert!(request.next().await.is_none());
15744    assert_eq!(
15745        counter.load(atomic::Ordering::Acquire),
15746        4,
15747        "With the completions menu open, only one LSP request should happen per input"
15748    );
15749}
15750
15751#[gpui::test]
15752async fn test_toggle_comment(cx: &mut TestAppContext) {
15753    init_test(cx, |_| {});
15754    let mut cx = EditorTestContext::new(cx).await;
15755    let language = Arc::new(Language::new(
15756        LanguageConfig {
15757            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15758            ..Default::default()
15759        },
15760        Some(tree_sitter_rust::LANGUAGE.into()),
15761    ));
15762    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15763
15764    // If multiple selections intersect a line, the line is only toggled once.
15765    cx.set_state(indoc! {"
15766        fn a() {
15767            «//b();
15768            ˇ»// «c();
15769            //ˇ»  d();
15770        }
15771    "});
15772
15773    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15774
15775    cx.assert_editor_state(indoc! {"
15776        fn a() {
15777            «b();
15778            c();
15779            ˇ» d();
15780        }
15781    "});
15782
15783    // The comment prefix is inserted at the same column for every line in a
15784    // selection.
15785    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15786
15787    cx.assert_editor_state(indoc! {"
15788        fn a() {
15789            // «b();
15790            // c();
15791            ˇ»//  d();
15792        }
15793    "});
15794
15795    // If a selection ends at the beginning of a line, that line is not toggled.
15796    cx.set_selections_state(indoc! {"
15797        fn a() {
15798            // b();
15799            «// c();
15800        ˇ»    //  d();
15801        }
15802    "});
15803
15804    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15805
15806    cx.assert_editor_state(indoc! {"
15807        fn a() {
15808            // b();
15809            «c();
15810        ˇ»    //  d();
15811        }
15812    "});
15813
15814    // If a selection span a single line and is empty, the line is toggled.
15815    cx.set_state(indoc! {"
15816        fn a() {
15817            a();
15818            b();
15819        ˇ
15820        }
15821    "});
15822
15823    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15824
15825    cx.assert_editor_state(indoc! {"
15826        fn a() {
15827            a();
15828            b();
15829        //•ˇ
15830        }
15831    "});
15832
15833    // If a selection span multiple lines, empty lines are not toggled.
15834    cx.set_state(indoc! {"
15835        fn a() {
15836            «a();
15837
15838            c();ˇ»
15839        }
15840    "});
15841
15842    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15843
15844    cx.assert_editor_state(indoc! {"
15845        fn a() {
15846            // «a();
15847
15848            // c();ˇ»
15849        }
15850    "});
15851
15852    // If a selection includes multiple comment prefixes, all lines are uncommented.
15853    cx.set_state(indoc! {"
15854        fn a() {
15855            «// a();
15856            /// b();
15857            //! c();ˇ»
15858        }
15859    "});
15860
15861    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15862
15863    cx.assert_editor_state(indoc! {"
15864        fn a() {
15865            «a();
15866            b();
15867            c();ˇ»
15868        }
15869    "});
15870}
15871
15872#[gpui::test]
15873async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
15874    init_test(cx, |_| {});
15875    let mut cx = EditorTestContext::new(cx).await;
15876    let language = Arc::new(Language::new(
15877        LanguageConfig {
15878            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15879            ..Default::default()
15880        },
15881        Some(tree_sitter_rust::LANGUAGE.into()),
15882    ));
15883    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15884
15885    let toggle_comments = &ToggleComments {
15886        advance_downwards: false,
15887        ignore_indent: true,
15888    };
15889
15890    // If multiple selections intersect a line, the line is only toggled once.
15891    cx.set_state(indoc! {"
15892        fn a() {
15893        //    «b();
15894        //    c();
15895        //    ˇ» d();
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            «b();
15904            c();
15905            ˇ» d();
15906        }
15907    "});
15908
15909    // The comment prefix is inserted at the beginning of each line
15910    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15911
15912    cx.assert_editor_state(indoc! {"
15913        fn a() {
15914        //    «b();
15915        //    c();
15916        //    ˇ» d();
15917        }
15918    "});
15919
15920    // If a selection ends at the beginning of a line, that line is not toggled.
15921    cx.set_selections_state(indoc! {"
15922        fn a() {
15923        //    b();
15924        //    «c();
15925        ˇ»//     d();
15926        }
15927    "});
15928
15929    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15930
15931    cx.assert_editor_state(indoc! {"
15932        fn a() {
15933        //    b();
15934            «c();
15935        ˇ»//     d();
15936        }
15937    "});
15938
15939    // If a selection span a single line and is empty, the line is toggled.
15940    cx.set_state(indoc! {"
15941        fn a() {
15942            a();
15943            b();
15944        ˇ
15945        }
15946    "});
15947
15948    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15949
15950    cx.assert_editor_state(indoc! {"
15951        fn a() {
15952            a();
15953            b();
15954        //ˇ
15955        }
15956    "});
15957
15958    // If a selection span multiple lines, empty lines are not toggled.
15959    cx.set_state(indoc! {"
15960        fn a() {
15961            «a();
15962
15963            c();ˇ»
15964        }
15965    "});
15966
15967    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15968
15969    cx.assert_editor_state(indoc! {"
15970        fn a() {
15971        //    «a();
15972
15973        //    c();ˇ»
15974        }
15975    "});
15976
15977    // If a selection includes multiple comment prefixes, all lines are uncommented.
15978    cx.set_state(indoc! {"
15979        fn a() {
15980        //    «a();
15981        ///    b();
15982        //!    c();ˇ»
15983        }
15984    "});
15985
15986    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15987
15988    cx.assert_editor_state(indoc! {"
15989        fn a() {
15990            «a();
15991            b();
15992            c();ˇ»
15993        }
15994    "});
15995}
15996
15997#[gpui::test]
15998async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
15999    init_test(cx, |_| {});
16000
16001    let language = Arc::new(Language::new(
16002        LanguageConfig {
16003            line_comments: vec!["// ".into()],
16004            ..Default::default()
16005        },
16006        Some(tree_sitter_rust::LANGUAGE.into()),
16007    ));
16008
16009    let mut cx = EditorTestContext::new(cx).await;
16010
16011    cx.language_registry().add(language.clone());
16012    cx.update_buffer(|buffer, cx| {
16013        buffer.set_language(Some(language), cx);
16014    });
16015
16016    let toggle_comments = &ToggleComments {
16017        advance_downwards: true,
16018        ignore_indent: false,
16019    };
16020
16021    // Single cursor on one line -> advance
16022    // Cursor moves horizontally 3 characters as well on non-blank line
16023    cx.set_state(indoc!(
16024        "fn a() {
16025             ˇdog();
16026             cat();
16027        }"
16028    ));
16029    cx.update_editor(|editor, window, cx| {
16030        editor.toggle_comments(toggle_comments, window, cx);
16031    });
16032    cx.assert_editor_state(indoc!(
16033        "fn a() {
16034             // dog();
16035             catˇ();
16036        }"
16037    ));
16038
16039    // Single selection on one line -> don't advance
16040    cx.set_state(indoc!(
16041        "fn a() {
16042             «dog()ˇ»;
16043             cat();
16044        }"
16045    ));
16046    cx.update_editor(|editor, window, cx| {
16047        editor.toggle_comments(toggle_comments, window, cx);
16048    });
16049    cx.assert_editor_state(indoc!(
16050        "fn a() {
16051             // «dog()ˇ»;
16052             cat();
16053        }"
16054    ));
16055
16056    // Multiple cursors on one line -> advance
16057    cx.set_state(indoc!(
16058        "fn a() {
16059             ˇdˇog();
16060             cat();
16061        }"
16062    ));
16063    cx.update_editor(|editor, window, cx| {
16064        editor.toggle_comments(toggle_comments, window, cx);
16065    });
16066    cx.assert_editor_state(indoc!(
16067        "fn a() {
16068             // dog();
16069             catˇ(ˇ);
16070        }"
16071    ));
16072
16073    // Multiple cursors on one line, with selection -> don't advance
16074    cx.set_state(indoc!(
16075        "fn a() {
16076             ˇdˇog«()ˇ»;
16077             cat();
16078        }"
16079    ));
16080    cx.update_editor(|editor, window, cx| {
16081        editor.toggle_comments(toggle_comments, window, cx);
16082    });
16083    cx.assert_editor_state(indoc!(
16084        "fn a() {
16085             // ˇdˇog«()ˇ»;
16086             cat();
16087        }"
16088    ));
16089
16090    // Single cursor on one line -> advance
16091    // Cursor moves to column 0 on blank line
16092    cx.set_state(indoc!(
16093        "fn a() {
16094             ˇdog();
16095
16096             cat();
16097        }"
16098    ));
16099    cx.update_editor(|editor, window, cx| {
16100        editor.toggle_comments(toggle_comments, window, cx);
16101    });
16102    cx.assert_editor_state(indoc!(
16103        "fn a() {
16104             // dog();
16105        ˇ
16106             cat();
16107        }"
16108    ));
16109
16110    // Single cursor on one line -> advance
16111    // Cursor starts and ends at column 0
16112    cx.set_state(indoc!(
16113        "fn a() {
16114         ˇ    dog();
16115             cat();
16116        }"
16117    ));
16118    cx.update_editor(|editor, window, cx| {
16119        editor.toggle_comments(toggle_comments, window, cx);
16120    });
16121    cx.assert_editor_state(indoc!(
16122        "fn a() {
16123             // dog();
16124         ˇ    cat();
16125        }"
16126    ));
16127}
16128
16129#[gpui::test]
16130async fn test_toggle_block_comment(cx: &mut TestAppContext) {
16131    init_test(cx, |_| {});
16132
16133    let mut cx = EditorTestContext::new(cx).await;
16134
16135    let html_language = Arc::new(
16136        Language::new(
16137            LanguageConfig {
16138                name: "HTML".into(),
16139                block_comment: Some(BlockCommentConfig {
16140                    start: "<!-- ".into(),
16141                    prefix: "".into(),
16142                    end: " -->".into(),
16143                    tab_size: 0,
16144                }),
16145                ..Default::default()
16146            },
16147            Some(tree_sitter_html::LANGUAGE.into()),
16148        )
16149        .with_injection_query(
16150            r#"
16151            (script_element
16152                (raw_text) @injection.content
16153                (#set! injection.language "javascript"))
16154            "#,
16155        )
16156        .unwrap(),
16157    );
16158
16159    let javascript_language = Arc::new(Language::new(
16160        LanguageConfig {
16161            name: "JavaScript".into(),
16162            line_comments: vec!["// ".into()],
16163            ..Default::default()
16164        },
16165        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
16166    ));
16167
16168    cx.language_registry().add(html_language.clone());
16169    cx.language_registry().add(javascript_language);
16170    cx.update_buffer(|buffer, cx| {
16171        buffer.set_language(Some(html_language), cx);
16172    });
16173
16174    // Toggle comments for empty selections
16175    cx.set_state(
16176        &r#"
16177            <p>A</p>ˇ
16178            <p>B</p>ˇ
16179            <p>C</p>ˇ
16180        "#
16181        .unindent(),
16182    );
16183    cx.update_editor(|editor, window, cx| {
16184        editor.toggle_comments(&ToggleComments::default(), window, cx)
16185    });
16186    cx.assert_editor_state(
16187        &r#"
16188            <!-- <p>A</p>ˇ -->
16189            <!-- <p>B</p>ˇ -->
16190            <!-- <p>C</p>ˇ -->
16191        "#
16192        .unindent(),
16193    );
16194    cx.update_editor(|editor, window, cx| {
16195        editor.toggle_comments(&ToggleComments::default(), window, cx)
16196    });
16197    cx.assert_editor_state(
16198        &r#"
16199            <p>A</p>ˇ
16200            <p>B</p>ˇ
16201            <p>C</p>ˇ
16202        "#
16203        .unindent(),
16204    );
16205
16206    // Toggle comments for mixture of empty and non-empty selections, where
16207    // multiple selections occupy a given line.
16208    cx.set_state(
16209        &r#"
16210            <p>A«</p>
16211            <p>ˇ»B</p>ˇ
16212            <p>C«</p>
16213            <p>ˇ»D</p>ˇ
16214        "#
16215        .unindent(),
16216    );
16217
16218    cx.update_editor(|editor, window, cx| {
16219        editor.toggle_comments(&ToggleComments::default(), window, cx)
16220    });
16221    cx.assert_editor_state(
16222        &r#"
16223            <!-- <p>A«</p>
16224            <p>ˇ»B</p>ˇ -->
16225            <!-- <p>C«</p>
16226            <p>ˇ»D</p>ˇ -->
16227        "#
16228        .unindent(),
16229    );
16230    cx.update_editor(|editor, window, cx| {
16231        editor.toggle_comments(&ToggleComments::default(), window, cx)
16232    });
16233    cx.assert_editor_state(
16234        &r#"
16235            <p>A«</p>
16236            <p>ˇ»B</p>ˇ
16237            <p>C«</p>
16238            <p>ˇ»D</p>ˇ
16239        "#
16240        .unindent(),
16241    );
16242
16243    // Toggle comments when different languages are active for different
16244    // selections.
16245    cx.set_state(
16246        &r#"
16247            ˇ<script>
16248                ˇvar x = new Y();
16249            ˇ</script>
16250        "#
16251        .unindent(),
16252    );
16253    cx.executor().run_until_parked();
16254    cx.update_editor(|editor, window, cx| {
16255        editor.toggle_comments(&ToggleComments::default(), window, cx)
16256    });
16257    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
16258    // Uncommenting and commenting from this position brings in even more wrong artifacts.
16259    cx.assert_editor_state(
16260        &r#"
16261            <!-- ˇ<script> -->
16262                // ˇvar x = new Y();
16263            <!-- ˇ</script> -->
16264        "#
16265        .unindent(),
16266    );
16267}
16268
16269#[gpui::test]
16270fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
16271    init_test(cx, |_| {});
16272
16273    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16274    let multibuffer = cx.new(|cx| {
16275        let mut multibuffer = MultiBuffer::new(ReadWrite);
16276        multibuffer.push_excerpts(
16277            buffer.clone(),
16278            [
16279                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
16280                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
16281            ],
16282            cx,
16283        );
16284        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
16285        multibuffer
16286    });
16287
16288    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16289    editor.update_in(cx, |editor, window, cx| {
16290        assert_eq!(editor.text(cx), "aaaa\nbbbb");
16291        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16292            s.select_ranges([
16293                Point::new(0, 0)..Point::new(0, 0),
16294                Point::new(1, 0)..Point::new(1, 0),
16295            ])
16296        });
16297
16298        editor.handle_input("X", window, cx);
16299        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
16300        assert_eq!(
16301            editor.selections.ranges(&editor.display_snapshot(cx)),
16302            [
16303                Point::new(0, 1)..Point::new(0, 1),
16304                Point::new(1, 1)..Point::new(1, 1),
16305            ]
16306        );
16307
16308        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
16309        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16310            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
16311        });
16312        editor.backspace(&Default::default(), window, cx);
16313        assert_eq!(editor.text(cx), "Xa\nbbb");
16314        assert_eq!(
16315            editor.selections.ranges(&editor.display_snapshot(cx)),
16316            [Point::new(1, 0)..Point::new(1, 0)]
16317        );
16318
16319        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16320            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
16321        });
16322        editor.backspace(&Default::default(), window, cx);
16323        assert_eq!(editor.text(cx), "X\nbb");
16324        assert_eq!(
16325            editor.selections.ranges(&editor.display_snapshot(cx)),
16326            [Point::new(0, 1)..Point::new(0, 1)]
16327        );
16328    });
16329}
16330
16331#[gpui::test]
16332fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
16333    init_test(cx, |_| {});
16334
16335    let markers = vec![('[', ']').into(), ('(', ')').into()];
16336    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
16337        indoc! {"
16338            [aaaa
16339            (bbbb]
16340            cccc)",
16341        },
16342        markers.clone(),
16343    );
16344    let excerpt_ranges = markers.into_iter().map(|marker| {
16345        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
16346        ExcerptRange::new(context)
16347    });
16348    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
16349    let multibuffer = cx.new(|cx| {
16350        let mut multibuffer = MultiBuffer::new(ReadWrite);
16351        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
16352        multibuffer
16353    });
16354
16355    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16356    editor.update_in(cx, |editor, window, cx| {
16357        let (expected_text, selection_ranges) = marked_text_ranges(
16358            indoc! {"
16359                aaaa
16360                bˇbbb
16361                bˇbbˇb
16362                cccc"
16363            },
16364            true,
16365        );
16366        assert_eq!(editor.text(cx), expected_text);
16367        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16368            s.select_ranges(selection_ranges)
16369        });
16370
16371        editor.handle_input("X", window, cx);
16372
16373        let (expected_text, expected_selections) = marked_text_ranges(
16374            indoc! {"
16375                aaaa
16376                bXˇbbXb
16377                bXˇbbXˇb
16378                cccc"
16379            },
16380            false,
16381        );
16382        assert_eq!(editor.text(cx), expected_text);
16383        assert_eq!(
16384            editor.selections.ranges(&editor.display_snapshot(cx)),
16385            expected_selections
16386        );
16387
16388        editor.newline(&Newline, window, cx);
16389        let (expected_text, expected_selections) = marked_text_ranges(
16390            indoc! {"
16391                aaaa
16392                bX
16393                ˇbbX
16394                b
16395                bX
16396                ˇbbX
16397                ˇb
16398                cccc"
16399            },
16400            false,
16401        );
16402        assert_eq!(editor.text(cx), expected_text);
16403        assert_eq!(
16404            editor.selections.ranges(&editor.display_snapshot(cx)),
16405            expected_selections
16406        );
16407    });
16408}
16409
16410#[gpui::test]
16411fn test_refresh_selections(cx: &mut TestAppContext) {
16412    init_test(cx, |_| {});
16413
16414    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16415    let mut excerpt1_id = None;
16416    let multibuffer = cx.new(|cx| {
16417        let mut multibuffer = MultiBuffer::new(ReadWrite);
16418        excerpt1_id = multibuffer
16419            .push_excerpts(
16420                buffer.clone(),
16421                [
16422                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16423                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16424                ],
16425                cx,
16426            )
16427            .into_iter()
16428            .next();
16429        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16430        multibuffer
16431    });
16432
16433    let editor = cx.add_window(|window, cx| {
16434        let mut editor = build_editor(multibuffer.clone(), window, cx);
16435        let snapshot = editor.snapshot(window, cx);
16436        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16437            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
16438        });
16439        editor.begin_selection(
16440            Point::new(2, 1).to_display_point(&snapshot),
16441            true,
16442            1,
16443            window,
16444            cx,
16445        );
16446        assert_eq!(
16447            editor.selections.ranges(&editor.display_snapshot(cx)),
16448            [
16449                Point::new(1, 3)..Point::new(1, 3),
16450                Point::new(2, 1)..Point::new(2, 1),
16451            ]
16452        );
16453        editor
16454    });
16455
16456    // Refreshing selections is a no-op when excerpts haven't changed.
16457    _ = editor.update(cx, |editor, window, cx| {
16458        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16459        assert_eq!(
16460            editor.selections.ranges(&editor.display_snapshot(cx)),
16461            [
16462                Point::new(1, 3)..Point::new(1, 3),
16463                Point::new(2, 1)..Point::new(2, 1),
16464            ]
16465        );
16466    });
16467
16468    multibuffer.update(cx, |multibuffer, cx| {
16469        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16470    });
16471    _ = editor.update(cx, |editor, window, cx| {
16472        // Removing an excerpt causes the first selection to become degenerate.
16473        assert_eq!(
16474            editor.selections.ranges(&editor.display_snapshot(cx)),
16475            [
16476                Point::new(0, 0)..Point::new(0, 0),
16477                Point::new(0, 1)..Point::new(0, 1)
16478            ]
16479        );
16480
16481        // Refreshing selections will relocate the first selection to the original buffer
16482        // location.
16483        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16484        assert_eq!(
16485            editor.selections.ranges(&editor.display_snapshot(cx)),
16486            [
16487                Point::new(0, 1)..Point::new(0, 1),
16488                Point::new(0, 3)..Point::new(0, 3)
16489            ]
16490        );
16491        assert!(editor.selections.pending_anchor().is_some());
16492    });
16493}
16494
16495#[gpui::test]
16496fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
16497    init_test(cx, |_| {});
16498
16499    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16500    let mut excerpt1_id = None;
16501    let multibuffer = cx.new(|cx| {
16502        let mut multibuffer = MultiBuffer::new(ReadWrite);
16503        excerpt1_id = multibuffer
16504            .push_excerpts(
16505                buffer.clone(),
16506                [
16507                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16508                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16509                ],
16510                cx,
16511            )
16512            .into_iter()
16513            .next();
16514        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16515        multibuffer
16516    });
16517
16518    let editor = cx.add_window(|window, cx| {
16519        let mut editor = build_editor(multibuffer.clone(), window, cx);
16520        let snapshot = editor.snapshot(window, cx);
16521        editor.begin_selection(
16522            Point::new(1, 3).to_display_point(&snapshot),
16523            false,
16524            1,
16525            window,
16526            cx,
16527        );
16528        assert_eq!(
16529            editor.selections.ranges(&editor.display_snapshot(cx)),
16530            [Point::new(1, 3)..Point::new(1, 3)]
16531        );
16532        editor
16533    });
16534
16535    multibuffer.update(cx, |multibuffer, cx| {
16536        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16537    });
16538    _ = editor.update(cx, |editor, window, cx| {
16539        assert_eq!(
16540            editor.selections.ranges(&editor.display_snapshot(cx)),
16541            [Point::new(0, 0)..Point::new(0, 0)]
16542        );
16543
16544        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
16545        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16546        assert_eq!(
16547            editor.selections.ranges(&editor.display_snapshot(cx)),
16548            [Point::new(0, 3)..Point::new(0, 3)]
16549        );
16550        assert!(editor.selections.pending_anchor().is_some());
16551    });
16552}
16553
16554#[gpui::test]
16555async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
16556    init_test(cx, |_| {});
16557
16558    let language = Arc::new(
16559        Language::new(
16560            LanguageConfig {
16561                brackets: BracketPairConfig {
16562                    pairs: vec![
16563                        BracketPair {
16564                            start: "{".to_string(),
16565                            end: "}".to_string(),
16566                            close: true,
16567                            surround: true,
16568                            newline: true,
16569                        },
16570                        BracketPair {
16571                            start: "/* ".to_string(),
16572                            end: " */".to_string(),
16573                            close: true,
16574                            surround: true,
16575                            newline: true,
16576                        },
16577                    ],
16578                    ..Default::default()
16579                },
16580                ..Default::default()
16581            },
16582            Some(tree_sitter_rust::LANGUAGE.into()),
16583        )
16584        .with_indents_query("")
16585        .unwrap(),
16586    );
16587
16588    let text = concat!(
16589        "{   }\n",     //
16590        "  x\n",       //
16591        "  /*   */\n", //
16592        "x\n",         //
16593        "{{} }\n",     //
16594    );
16595
16596    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
16597    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16598    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16599    editor
16600        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
16601        .await;
16602
16603    editor.update_in(cx, |editor, window, cx| {
16604        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16605            s.select_display_ranges([
16606                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
16607                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
16608                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
16609            ])
16610        });
16611        editor.newline(&Newline, window, cx);
16612
16613        assert_eq!(
16614            editor.buffer().read(cx).read(cx).text(),
16615            concat!(
16616                "{ \n",    // Suppress rustfmt
16617                "\n",      //
16618                "}\n",     //
16619                "  x\n",   //
16620                "  /* \n", //
16621                "  \n",    //
16622                "  */\n",  //
16623                "x\n",     //
16624                "{{} \n",  //
16625                "}\n",     //
16626            )
16627        );
16628    });
16629}
16630
16631#[gpui::test]
16632fn test_highlighted_ranges(cx: &mut TestAppContext) {
16633    init_test(cx, |_| {});
16634
16635    let editor = cx.add_window(|window, cx| {
16636        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
16637        build_editor(buffer, window, cx)
16638    });
16639
16640    _ = editor.update(cx, |editor, window, cx| {
16641        struct Type1;
16642        struct Type2;
16643
16644        let buffer = editor.buffer.read(cx).snapshot(cx);
16645
16646        let anchor_range =
16647            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
16648
16649        editor.highlight_background::<Type1>(
16650            &[
16651                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
16652                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
16653                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
16654                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
16655            ],
16656            |_| Hsla::red(),
16657            cx,
16658        );
16659        editor.highlight_background::<Type2>(
16660            &[
16661                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
16662                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
16663                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
16664                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
16665            ],
16666            |_| Hsla::green(),
16667            cx,
16668        );
16669
16670        let snapshot = editor.snapshot(window, cx);
16671        let highlighted_ranges = editor.sorted_background_highlights_in_range(
16672            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
16673            &snapshot,
16674            cx.theme(),
16675        );
16676        assert_eq!(
16677            highlighted_ranges,
16678            &[
16679                (
16680                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
16681                    Hsla::green(),
16682                ),
16683                (
16684                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
16685                    Hsla::red(),
16686                ),
16687                (
16688                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
16689                    Hsla::green(),
16690                ),
16691                (
16692                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16693                    Hsla::red(),
16694                ),
16695            ]
16696        );
16697        assert_eq!(
16698            editor.sorted_background_highlights_in_range(
16699                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
16700                &snapshot,
16701                cx.theme(),
16702            ),
16703            &[(
16704                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16705                Hsla::red(),
16706            )]
16707        );
16708    });
16709}
16710
16711#[gpui::test]
16712async fn test_following(cx: &mut TestAppContext) {
16713    init_test(cx, |_| {});
16714
16715    let fs = FakeFs::new(cx.executor());
16716    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16717
16718    let buffer = project.update(cx, |project, cx| {
16719        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
16720        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
16721    });
16722    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
16723    let follower = cx.update(|cx| {
16724        cx.open_window(
16725            WindowOptions {
16726                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
16727                    gpui::Point::new(px(0.), px(0.)),
16728                    gpui::Point::new(px(10.), px(80.)),
16729                ))),
16730                ..Default::default()
16731            },
16732            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
16733        )
16734        .unwrap()
16735    });
16736
16737    let is_still_following = Rc::new(RefCell::new(true));
16738    let follower_edit_event_count = Rc::new(RefCell::new(0));
16739    let pending_update = Rc::new(RefCell::new(None));
16740    let leader_entity = leader.root(cx).unwrap();
16741    let follower_entity = follower.root(cx).unwrap();
16742    _ = follower.update(cx, {
16743        let update = pending_update.clone();
16744        let is_still_following = is_still_following.clone();
16745        let follower_edit_event_count = follower_edit_event_count.clone();
16746        |_, window, cx| {
16747            cx.subscribe_in(
16748                &leader_entity,
16749                window,
16750                move |_, leader, event, window, cx| {
16751                    leader.read(cx).add_event_to_update_proto(
16752                        event,
16753                        &mut update.borrow_mut(),
16754                        window,
16755                        cx,
16756                    );
16757                },
16758            )
16759            .detach();
16760
16761            cx.subscribe_in(
16762                &follower_entity,
16763                window,
16764                move |_, _, event: &EditorEvent, _window, _cx| {
16765                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
16766                        *is_still_following.borrow_mut() = false;
16767                    }
16768
16769                    if let EditorEvent::BufferEdited = event {
16770                        *follower_edit_event_count.borrow_mut() += 1;
16771                    }
16772                },
16773            )
16774            .detach();
16775        }
16776    });
16777
16778    // Update the selections only
16779    _ = leader.update(cx, |leader, window, cx| {
16780        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16781            s.select_ranges([1..1])
16782        });
16783    });
16784    follower
16785        .update(cx, |follower, window, cx| {
16786            follower.apply_update_proto(
16787                &project,
16788                pending_update.borrow_mut().take().unwrap(),
16789                window,
16790                cx,
16791            )
16792        })
16793        .unwrap()
16794        .await
16795        .unwrap();
16796    _ = follower.update(cx, |follower, _, cx| {
16797        assert_eq!(
16798            follower.selections.ranges(&follower.display_snapshot(cx)),
16799            vec![1..1]
16800        );
16801    });
16802    assert!(*is_still_following.borrow());
16803    assert_eq!(*follower_edit_event_count.borrow(), 0);
16804
16805    // Update the scroll position only
16806    _ = leader.update(cx, |leader, window, cx| {
16807        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16808    });
16809    follower
16810        .update(cx, |follower, window, cx| {
16811            follower.apply_update_proto(
16812                &project,
16813                pending_update.borrow_mut().take().unwrap(),
16814                window,
16815                cx,
16816            )
16817        })
16818        .unwrap()
16819        .await
16820        .unwrap();
16821    assert_eq!(
16822        follower
16823            .update(cx, |follower, _, cx| follower.scroll_position(cx))
16824            .unwrap(),
16825        gpui::Point::new(1.5, 3.5)
16826    );
16827    assert!(*is_still_following.borrow());
16828    assert_eq!(*follower_edit_event_count.borrow(), 0);
16829
16830    // Update the selections and scroll position. The follower's scroll position is updated
16831    // via autoscroll, not via the leader's exact scroll position.
16832    _ = leader.update(cx, |leader, window, cx| {
16833        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16834            s.select_ranges([0..0])
16835        });
16836        leader.request_autoscroll(Autoscroll::newest(), cx);
16837        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16838    });
16839    follower
16840        .update(cx, |follower, window, cx| {
16841            follower.apply_update_proto(
16842                &project,
16843                pending_update.borrow_mut().take().unwrap(),
16844                window,
16845                cx,
16846            )
16847        })
16848        .unwrap()
16849        .await
16850        .unwrap();
16851    _ = follower.update(cx, |follower, _, cx| {
16852        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
16853        assert_eq!(
16854            follower.selections.ranges(&follower.display_snapshot(cx)),
16855            vec![0..0]
16856        );
16857    });
16858    assert!(*is_still_following.borrow());
16859
16860    // Creating a pending selection that precedes another selection
16861    _ = leader.update(cx, |leader, window, cx| {
16862        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16863            s.select_ranges([1..1])
16864        });
16865        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
16866    });
16867    follower
16868        .update(cx, |follower, window, cx| {
16869            follower.apply_update_proto(
16870                &project,
16871                pending_update.borrow_mut().take().unwrap(),
16872                window,
16873                cx,
16874            )
16875        })
16876        .unwrap()
16877        .await
16878        .unwrap();
16879    _ = follower.update(cx, |follower, _, cx| {
16880        assert_eq!(
16881            follower.selections.ranges(&follower.display_snapshot(cx)),
16882            vec![0..0, 1..1]
16883        );
16884    });
16885    assert!(*is_still_following.borrow());
16886
16887    // Extend the pending selection so that it surrounds another selection
16888    _ = leader.update(cx, |leader, window, cx| {
16889        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
16890    });
16891    follower
16892        .update(cx, |follower, window, cx| {
16893            follower.apply_update_proto(
16894                &project,
16895                pending_update.borrow_mut().take().unwrap(),
16896                window,
16897                cx,
16898            )
16899        })
16900        .unwrap()
16901        .await
16902        .unwrap();
16903    _ = follower.update(cx, |follower, _, cx| {
16904        assert_eq!(
16905            follower.selections.ranges(&follower.display_snapshot(cx)),
16906            vec![0..2]
16907        );
16908    });
16909
16910    // Scrolling locally breaks the follow
16911    _ = follower.update(cx, |follower, window, cx| {
16912        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
16913        follower.set_scroll_anchor(
16914            ScrollAnchor {
16915                anchor: top_anchor,
16916                offset: gpui::Point::new(0.0, 0.5),
16917            },
16918            window,
16919            cx,
16920        );
16921    });
16922    assert!(!(*is_still_following.borrow()));
16923}
16924
16925#[gpui::test]
16926async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
16927    init_test(cx, |_| {});
16928
16929    let fs = FakeFs::new(cx.executor());
16930    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16931    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16932    let pane = workspace
16933        .update(cx, |workspace, _, _| workspace.active_pane().clone())
16934        .unwrap();
16935
16936    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16937
16938    let leader = pane.update_in(cx, |_, window, cx| {
16939        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
16940        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
16941    });
16942
16943    // Start following the editor when it has no 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_1 = 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
16965    let update_message = Rc::new(RefCell::new(None));
16966    follower_1.update_in(cx, {
16967        let update = update_message.clone();
16968        |_, window, cx| {
16969            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
16970                leader.read(cx).add_event_to_update_proto(
16971                    event,
16972                    &mut update.borrow_mut(),
16973                    window,
16974                    cx,
16975                );
16976            })
16977            .detach();
16978        }
16979    });
16980
16981    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
16982        (
16983            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
16984            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
16985        )
16986    });
16987
16988    // Insert some excerpts.
16989    leader.update(cx, |leader, cx| {
16990        leader.buffer.update(cx, |multibuffer, cx| {
16991            multibuffer.set_excerpts_for_path(
16992                PathKey::with_sort_prefix(1, rel_path("b.txt").into_arc()),
16993                buffer_1.clone(),
16994                vec![
16995                    Point::row_range(0..3),
16996                    Point::row_range(1..6),
16997                    Point::row_range(12..15),
16998                ],
16999                0,
17000                cx,
17001            );
17002            multibuffer.set_excerpts_for_path(
17003                PathKey::with_sort_prefix(1, rel_path("a.txt").into_arc()),
17004                buffer_2.clone(),
17005                vec![Point::row_range(0..6), Point::row_range(8..12)],
17006                0,
17007                cx,
17008            );
17009        });
17010    });
17011
17012    // Apply the update of adding the excerpts.
17013    follower_1
17014        .update_in(cx, |follower, window, cx| {
17015            follower.apply_update_proto(
17016                &project,
17017                update_message.borrow().clone().unwrap(),
17018                window,
17019                cx,
17020            )
17021        })
17022        .await
17023        .unwrap();
17024    assert_eq!(
17025        follower_1.update(cx, |editor, cx| editor.text(cx)),
17026        leader.update(cx, |editor, cx| editor.text(cx))
17027    );
17028    update_message.borrow_mut().take();
17029
17030    // Start following separately after it already has excerpts.
17031    let mut state_message =
17032        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
17033    let workspace_entity = workspace.root(cx).unwrap();
17034    let follower_2 = cx
17035        .update_window(*workspace.deref(), |_, window, cx| {
17036            Editor::from_state_proto(
17037                workspace_entity,
17038                ViewId {
17039                    creator: CollaboratorId::PeerId(PeerId::default()),
17040                    id: 0,
17041                },
17042                &mut state_message,
17043                window,
17044                cx,
17045            )
17046        })
17047        .unwrap()
17048        .unwrap()
17049        .await
17050        .unwrap();
17051    assert_eq!(
17052        follower_2.update(cx, |editor, cx| editor.text(cx)),
17053        leader.update(cx, |editor, cx| editor.text(cx))
17054    );
17055
17056    // Remove some excerpts.
17057    leader.update(cx, |leader, cx| {
17058        leader.buffer.update(cx, |multibuffer, cx| {
17059            let excerpt_ids = multibuffer.excerpt_ids();
17060            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
17061            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
17062        });
17063    });
17064
17065    // Apply the update of removing the excerpts.
17066    follower_1
17067        .update_in(cx, |follower, window, cx| {
17068            follower.apply_update_proto(
17069                &project,
17070                update_message.borrow().clone().unwrap(),
17071                window,
17072                cx,
17073            )
17074        })
17075        .await
17076        .unwrap();
17077    follower_2
17078        .update_in(cx, |follower, window, cx| {
17079            follower.apply_update_proto(
17080                &project,
17081                update_message.borrow().clone().unwrap(),
17082                window,
17083                cx,
17084            )
17085        })
17086        .await
17087        .unwrap();
17088    update_message.borrow_mut().take();
17089    assert_eq!(
17090        follower_1.update(cx, |editor, cx| editor.text(cx)),
17091        leader.update(cx, |editor, cx| editor.text(cx))
17092    );
17093}
17094
17095#[gpui::test]
17096async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17097    init_test(cx, |_| {});
17098
17099    let mut cx = EditorTestContext::new(cx).await;
17100    let lsp_store =
17101        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
17102
17103    cx.set_state(indoc! {"
17104        ˇfn func(abc def: i32) -> u32 {
17105        }
17106    "});
17107
17108    cx.update(|_, cx| {
17109        lsp_store.update(cx, |lsp_store, cx| {
17110            lsp_store
17111                .update_diagnostics(
17112                    LanguageServerId(0),
17113                    lsp::PublishDiagnosticsParams {
17114                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
17115                        version: None,
17116                        diagnostics: vec![
17117                            lsp::Diagnostic {
17118                                range: lsp::Range::new(
17119                                    lsp::Position::new(0, 11),
17120                                    lsp::Position::new(0, 12),
17121                                ),
17122                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17123                                ..Default::default()
17124                            },
17125                            lsp::Diagnostic {
17126                                range: lsp::Range::new(
17127                                    lsp::Position::new(0, 12),
17128                                    lsp::Position::new(0, 15),
17129                                ),
17130                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17131                                ..Default::default()
17132                            },
17133                            lsp::Diagnostic {
17134                                range: lsp::Range::new(
17135                                    lsp::Position::new(0, 25),
17136                                    lsp::Position::new(0, 28),
17137                                ),
17138                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17139                                ..Default::default()
17140                            },
17141                        ],
17142                    },
17143                    None,
17144                    DiagnosticSourceKind::Pushed,
17145                    &[],
17146                    cx,
17147                )
17148                .unwrap()
17149        });
17150    });
17151
17152    executor.run_until_parked();
17153
17154    cx.update_editor(|editor, window, cx| {
17155        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17156    });
17157
17158    cx.assert_editor_state(indoc! {"
17159        fn func(abc def: i32) -> ˇu32 {
17160        }
17161    "});
17162
17163    cx.update_editor(|editor, window, cx| {
17164        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17165    });
17166
17167    cx.assert_editor_state(indoc! {"
17168        fn func(abc ˇdef: i32) -> u32 {
17169        }
17170    "});
17171
17172    cx.update_editor(|editor, window, cx| {
17173        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17174    });
17175
17176    cx.assert_editor_state(indoc! {"
17177        fn func(abcˇ def: i32) -> u32 {
17178        }
17179    "});
17180
17181    cx.update_editor(|editor, window, cx| {
17182        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17183    });
17184
17185    cx.assert_editor_state(indoc! {"
17186        fn func(abc def: i32) -> ˇu32 {
17187        }
17188    "});
17189}
17190
17191#[gpui::test]
17192async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17193    init_test(cx, |_| {});
17194
17195    let mut cx = EditorTestContext::new(cx).await;
17196
17197    let diff_base = r#"
17198        use some::mod;
17199
17200        const A: u32 = 42;
17201
17202        fn main() {
17203            println!("hello");
17204
17205            println!("world");
17206        }
17207        "#
17208    .unindent();
17209
17210    // Edits are modified, removed, modified, added
17211    cx.set_state(
17212        &r#"
17213        use some::modified;
17214
17215        ˇ
17216        fn main() {
17217            println!("hello there");
17218
17219            println!("around the");
17220            println!("world");
17221        }
17222        "#
17223        .unindent(),
17224    );
17225
17226    cx.set_head_text(&diff_base);
17227    executor.run_until_parked();
17228
17229    cx.update_editor(|editor, window, cx| {
17230        //Wrap around the bottom of the buffer
17231        for _ in 0..3 {
17232            editor.go_to_next_hunk(&GoToHunk, window, cx);
17233        }
17234    });
17235
17236    cx.assert_editor_state(
17237        &r#"
17238        ˇuse some::modified;
17239
17240
17241        fn main() {
17242            println!("hello there");
17243
17244            println!("around the");
17245            println!("world");
17246        }
17247        "#
17248        .unindent(),
17249    );
17250
17251    cx.update_editor(|editor, window, cx| {
17252        //Wrap around the top of the buffer
17253        for _ in 0..2 {
17254            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17255        }
17256    });
17257
17258    cx.assert_editor_state(
17259        &r#"
17260        use some::modified;
17261
17262
17263        fn main() {
17264        ˇ    println!("hello there");
17265
17266            println!("around the");
17267            println!("world");
17268        }
17269        "#
17270        .unindent(),
17271    );
17272
17273    cx.update_editor(|editor, window, cx| {
17274        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17275    });
17276
17277    cx.assert_editor_state(
17278        &r#"
17279        use some::modified;
17280
17281        ˇ
17282        fn main() {
17283            println!("hello there");
17284
17285            println!("around the");
17286            println!("world");
17287        }
17288        "#
17289        .unindent(),
17290    );
17291
17292    cx.update_editor(|editor, window, cx| {
17293        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17294    });
17295
17296    cx.assert_editor_state(
17297        &r#"
17298        ˇuse some::modified;
17299
17300
17301        fn main() {
17302            println!("hello there");
17303
17304            println!("around the");
17305            println!("world");
17306        }
17307        "#
17308        .unindent(),
17309    );
17310
17311    cx.update_editor(|editor, window, cx| {
17312        for _ in 0..2 {
17313            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17314        }
17315    });
17316
17317    cx.assert_editor_state(
17318        &r#"
17319        use some::modified;
17320
17321
17322        fn main() {
17323        ˇ    println!("hello there");
17324
17325            println!("around the");
17326            println!("world");
17327        }
17328        "#
17329        .unindent(),
17330    );
17331
17332    cx.update_editor(|editor, window, cx| {
17333        editor.fold(&Fold, window, cx);
17334    });
17335
17336    cx.update_editor(|editor, window, cx| {
17337        editor.go_to_next_hunk(&GoToHunk, window, cx);
17338    });
17339
17340    cx.assert_editor_state(
17341        &r#"
17342        ˇuse some::modified;
17343
17344
17345        fn main() {
17346            println!("hello there");
17347
17348            println!("around the");
17349            println!("world");
17350        }
17351        "#
17352        .unindent(),
17353    );
17354}
17355
17356#[test]
17357fn test_split_words() {
17358    fn split(text: &str) -> Vec<&str> {
17359        split_words(text).collect()
17360    }
17361
17362    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
17363    assert_eq!(split("hello_world"), &["hello_", "world"]);
17364    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
17365    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
17366    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
17367    assert_eq!(split("helloworld"), &["helloworld"]);
17368
17369    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
17370}
17371
17372#[gpui::test]
17373async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
17374    init_test(cx, |_| {});
17375
17376    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
17377    let mut assert = |before, after| {
17378        let _state_context = cx.set_state(before);
17379        cx.run_until_parked();
17380        cx.update_editor(|editor, window, cx| {
17381            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
17382        });
17383        cx.run_until_parked();
17384        cx.assert_editor_state(after);
17385    };
17386
17387    // Outside bracket jumps to outside of matching bracket
17388    assert("console.logˇ(var);", "console.log(var)ˇ;");
17389    assert("console.log(var)ˇ;", "console.logˇ(var);");
17390
17391    // Inside bracket jumps to inside of matching bracket
17392    assert("console.log(ˇvar);", "console.log(varˇ);");
17393    assert("console.log(varˇ);", "console.log(ˇvar);");
17394
17395    // When outside a bracket and inside, favor jumping to the inside bracket
17396    assert(
17397        "console.log('foo', [1, 2, 3]ˇ);",
17398        "console.log(ˇ'foo', [1, 2, 3]);",
17399    );
17400    assert(
17401        "console.log(ˇ'foo', [1, 2, 3]);",
17402        "console.log('foo', [1, 2, 3]ˇ);",
17403    );
17404
17405    // Bias forward if two options are equally likely
17406    assert(
17407        "let result = curried_fun()ˇ();",
17408        "let result = curried_fun()()ˇ;",
17409    );
17410
17411    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
17412    assert(
17413        indoc! {"
17414            function test() {
17415                console.log('test')ˇ
17416            }"},
17417        indoc! {"
17418            function test() {
17419                console.logˇ('test')
17420            }"},
17421    );
17422}
17423
17424#[gpui::test]
17425async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
17426    init_test(cx, |_| {});
17427
17428    let fs = FakeFs::new(cx.executor());
17429    fs.insert_tree(
17430        path!("/a"),
17431        json!({
17432            "main.rs": "fn main() { let a = 5; }",
17433            "other.rs": "// Test file",
17434        }),
17435    )
17436    .await;
17437    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17438
17439    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17440    language_registry.add(Arc::new(Language::new(
17441        LanguageConfig {
17442            name: "Rust".into(),
17443            matcher: LanguageMatcher {
17444                path_suffixes: vec!["rs".to_string()],
17445                ..Default::default()
17446            },
17447            brackets: BracketPairConfig {
17448                pairs: vec![BracketPair {
17449                    start: "{".to_string(),
17450                    end: "}".to_string(),
17451                    close: true,
17452                    surround: true,
17453                    newline: true,
17454                }],
17455                disabled_scopes_by_bracket_ix: Vec::new(),
17456            },
17457            ..Default::default()
17458        },
17459        Some(tree_sitter_rust::LANGUAGE.into()),
17460    )));
17461    let mut fake_servers = language_registry.register_fake_lsp(
17462        "Rust",
17463        FakeLspAdapter {
17464            capabilities: lsp::ServerCapabilities {
17465                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17466                    first_trigger_character: "{".to_string(),
17467                    more_trigger_character: None,
17468                }),
17469                ..Default::default()
17470            },
17471            ..Default::default()
17472        },
17473    );
17474
17475    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17476
17477    let cx = &mut VisualTestContext::from_window(*workspace, cx);
17478
17479    let worktree_id = workspace
17480        .update(cx, |workspace, _, cx| {
17481            workspace.project().update(cx, |project, cx| {
17482                project.worktrees(cx).next().unwrap().read(cx).id()
17483            })
17484        })
17485        .unwrap();
17486
17487    let buffer = project
17488        .update(cx, |project, cx| {
17489            project.open_local_buffer(path!("/a/main.rs"), cx)
17490        })
17491        .await
17492        .unwrap();
17493    let editor_handle = workspace
17494        .update(cx, |workspace, window, cx| {
17495            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
17496        })
17497        .unwrap()
17498        .await
17499        .unwrap()
17500        .downcast::<Editor>()
17501        .unwrap();
17502
17503    cx.executor().start_waiting();
17504    let fake_server = fake_servers.next().await.unwrap();
17505
17506    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
17507        |params, _| async move {
17508            assert_eq!(
17509                params.text_document_position.text_document.uri,
17510                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
17511            );
17512            assert_eq!(
17513                params.text_document_position.position,
17514                lsp::Position::new(0, 21),
17515            );
17516
17517            Ok(Some(vec![lsp::TextEdit {
17518                new_text: "]".to_string(),
17519                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17520            }]))
17521        },
17522    );
17523
17524    editor_handle.update_in(cx, |editor, window, cx| {
17525        window.focus(&editor.focus_handle(cx));
17526        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17527            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
17528        });
17529        editor.handle_input("{", window, cx);
17530    });
17531
17532    cx.executor().run_until_parked();
17533
17534    buffer.update(cx, |buffer, _| {
17535        assert_eq!(
17536            buffer.text(),
17537            "fn main() { let a = {5}; }",
17538            "No extra braces from on type formatting should appear in the buffer"
17539        )
17540    });
17541}
17542
17543#[gpui::test(iterations = 20, seeds(31))]
17544async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
17545    init_test(cx, |_| {});
17546
17547    let mut cx = EditorLspTestContext::new_rust(
17548        lsp::ServerCapabilities {
17549            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17550                first_trigger_character: ".".to_string(),
17551                more_trigger_character: None,
17552            }),
17553            ..Default::default()
17554        },
17555        cx,
17556    )
17557    .await;
17558
17559    cx.update_buffer(|buffer, _| {
17560        // This causes autoindent to be async.
17561        buffer.set_sync_parse_timeout(Duration::ZERO)
17562    });
17563
17564    cx.set_state("fn c() {\n    d()ˇ\n}\n");
17565    cx.simulate_keystroke("\n");
17566    cx.run_until_parked();
17567
17568    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
17569    let mut request =
17570        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
17571            let buffer_cloned = buffer_cloned.clone();
17572            async move {
17573                buffer_cloned.update(&mut cx, |buffer, _| {
17574                    assert_eq!(
17575                        buffer.text(),
17576                        "fn c() {\n    d()\n        .\n}\n",
17577                        "OnTypeFormatting should triggered after autoindent applied"
17578                    )
17579                })?;
17580
17581                Ok(Some(vec![]))
17582            }
17583        });
17584
17585    cx.simulate_keystroke(".");
17586    cx.run_until_parked();
17587
17588    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
17589    assert!(request.next().await.is_some());
17590    request.close();
17591    assert!(request.next().await.is_none());
17592}
17593
17594#[gpui::test]
17595async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
17596    init_test(cx, |_| {});
17597
17598    let fs = FakeFs::new(cx.executor());
17599    fs.insert_tree(
17600        path!("/a"),
17601        json!({
17602            "main.rs": "fn main() { let a = 5; }",
17603            "other.rs": "// Test file",
17604        }),
17605    )
17606    .await;
17607
17608    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17609
17610    let server_restarts = Arc::new(AtomicUsize::new(0));
17611    let closure_restarts = Arc::clone(&server_restarts);
17612    let language_server_name = "test language server";
17613    let language_name: LanguageName = "Rust".into();
17614
17615    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17616    language_registry.add(Arc::new(Language::new(
17617        LanguageConfig {
17618            name: language_name.clone(),
17619            matcher: LanguageMatcher {
17620                path_suffixes: vec!["rs".to_string()],
17621                ..Default::default()
17622            },
17623            ..Default::default()
17624        },
17625        Some(tree_sitter_rust::LANGUAGE.into()),
17626    )));
17627    let mut fake_servers = language_registry.register_fake_lsp(
17628        "Rust",
17629        FakeLspAdapter {
17630            name: language_server_name,
17631            initialization_options: Some(json!({
17632                "testOptionValue": true
17633            })),
17634            initializer: Some(Box::new(move |fake_server| {
17635                let task_restarts = Arc::clone(&closure_restarts);
17636                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
17637                    task_restarts.fetch_add(1, atomic::Ordering::Release);
17638                    futures::future::ready(Ok(()))
17639                });
17640            })),
17641            ..Default::default()
17642        },
17643    );
17644
17645    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17646    let _buffer = project
17647        .update(cx, |project, cx| {
17648            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
17649        })
17650        .await
17651        .unwrap();
17652    let _fake_server = fake_servers.next().await.unwrap();
17653    update_test_language_settings(cx, |language_settings| {
17654        language_settings.languages.0.insert(
17655            language_name.clone().0,
17656            LanguageSettingsContent {
17657                tab_size: NonZeroU32::new(8),
17658                ..Default::default()
17659            },
17660        );
17661    });
17662    cx.executor().run_until_parked();
17663    assert_eq!(
17664        server_restarts.load(atomic::Ordering::Acquire),
17665        0,
17666        "Should not restart LSP server on an unrelated change"
17667    );
17668
17669    update_test_project_settings(cx, |project_settings| {
17670        project_settings.lsp.insert(
17671            "Some other server name".into(),
17672            LspSettings {
17673                binary: None,
17674                settings: None,
17675                initialization_options: Some(json!({
17676                    "some other init value": false
17677                })),
17678                enable_lsp_tasks: false,
17679                fetch: None,
17680            },
17681        );
17682    });
17683    cx.executor().run_until_parked();
17684    assert_eq!(
17685        server_restarts.load(atomic::Ordering::Acquire),
17686        0,
17687        "Should not restart LSP server on an unrelated LSP settings change"
17688    );
17689
17690    update_test_project_settings(cx, |project_settings| {
17691        project_settings.lsp.insert(
17692            language_server_name.into(),
17693            LspSettings {
17694                binary: None,
17695                settings: None,
17696                initialization_options: Some(json!({
17697                    "anotherInitValue": false
17698                })),
17699                enable_lsp_tasks: false,
17700                fetch: None,
17701            },
17702        );
17703    });
17704    cx.executor().run_until_parked();
17705    assert_eq!(
17706        server_restarts.load(atomic::Ordering::Acquire),
17707        1,
17708        "Should restart LSP server on a related LSP settings change"
17709    );
17710
17711    update_test_project_settings(cx, |project_settings| {
17712        project_settings.lsp.insert(
17713            language_server_name.into(),
17714            LspSettings {
17715                binary: None,
17716                settings: None,
17717                initialization_options: Some(json!({
17718                    "anotherInitValue": false
17719                })),
17720                enable_lsp_tasks: false,
17721                fetch: None,
17722            },
17723        );
17724    });
17725    cx.executor().run_until_parked();
17726    assert_eq!(
17727        server_restarts.load(atomic::Ordering::Acquire),
17728        1,
17729        "Should not restart LSP server on a related LSP settings change that is the same"
17730    );
17731
17732    update_test_project_settings(cx, |project_settings| {
17733        project_settings.lsp.insert(
17734            language_server_name.into(),
17735            LspSettings {
17736                binary: None,
17737                settings: None,
17738                initialization_options: None,
17739                enable_lsp_tasks: false,
17740                fetch: None,
17741            },
17742        );
17743    });
17744    cx.executor().run_until_parked();
17745    assert_eq!(
17746        server_restarts.load(atomic::Ordering::Acquire),
17747        2,
17748        "Should restart LSP server on another related LSP settings change"
17749    );
17750}
17751
17752#[gpui::test]
17753async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
17754    init_test(cx, |_| {});
17755
17756    let mut cx = EditorLspTestContext::new_rust(
17757        lsp::ServerCapabilities {
17758            completion_provider: Some(lsp::CompletionOptions {
17759                trigger_characters: Some(vec![".".to_string()]),
17760                resolve_provider: Some(true),
17761                ..Default::default()
17762            }),
17763            ..Default::default()
17764        },
17765        cx,
17766    )
17767    .await;
17768
17769    cx.set_state("fn main() { let a = 2ˇ; }");
17770    cx.simulate_keystroke(".");
17771    let completion_item = lsp::CompletionItem {
17772        label: "some".into(),
17773        kind: Some(lsp::CompletionItemKind::SNIPPET),
17774        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
17775        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
17776            kind: lsp::MarkupKind::Markdown,
17777            value: "```rust\nSome(2)\n```".to_string(),
17778        })),
17779        deprecated: Some(false),
17780        sort_text: Some("fffffff2".to_string()),
17781        filter_text: Some("some".to_string()),
17782        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
17783        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17784            range: lsp::Range {
17785                start: lsp::Position {
17786                    line: 0,
17787                    character: 22,
17788                },
17789                end: lsp::Position {
17790                    line: 0,
17791                    character: 22,
17792                },
17793            },
17794            new_text: "Some(2)".to_string(),
17795        })),
17796        additional_text_edits: Some(vec![lsp::TextEdit {
17797            range: lsp::Range {
17798                start: lsp::Position {
17799                    line: 0,
17800                    character: 20,
17801                },
17802                end: lsp::Position {
17803                    line: 0,
17804                    character: 22,
17805                },
17806            },
17807            new_text: "".to_string(),
17808        }]),
17809        ..Default::default()
17810    };
17811
17812    let closure_completion_item = completion_item.clone();
17813    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17814        let task_completion_item = closure_completion_item.clone();
17815        async move {
17816            Ok(Some(lsp::CompletionResponse::Array(vec![
17817                task_completion_item,
17818            ])))
17819        }
17820    });
17821
17822    request.next().await;
17823
17824    cx.condition(|editor, _| editor.context_menu_visible())
17825        .await;
17826    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
17827        editor
17828            .confirm_completion(&ConfirmCompletion::default(), window, cx)
17829            .unwrap()
17830    });
17831    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
17832
17833    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
17834        let task_completion_item = completion_item.clone();
17835        async move { Ok(task_completion_item) }
17836    })
17837    .next()
17838    .await
17839    .unwrap();
17840    apply_additional_edits.await.unwrap();
17841    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
17842}
17843
17844#[gpui::test]
17845async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
17846    init_test(cx, |_| {});
17847
17848    let mut cx = EditorLspTestContext::new_rust(
17849        lsp::ServerCapabilities {
17850            completion_provider: Some(lsp::CompletionOptions {
17851                trigger_characters: Some(vec![".".to_string()]),
17852                resolve_provider: Some(true),
17853                ..Default::default()
17854            }),
17855            ..Default::default()
17856        },
17857        cx,
17858    )
17859    .await;
17860
17861    cx.set_state("fn main() { let a = 2ˇ; }");
17862    cx.simulate_keystroke(".");
17863
17864    let item1 = lsp::CompletionItem {
17865        label: "method id()".to_string(),
17866        filter_text: Some("id".to_string()),
17867        detail: None,
17868        documentation: None,
17869        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17870            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17871            new_text: ".id".to_string(),
17872        })),
17873        ..lsp::CompletionItem::default()
17874    };
17875
17876    let item2 = lsp::CompletionItem {
17877        label: "other".to_string(),
17878        filter_text: Some("other".to_string()),
17879        detail: None,
17880        documentation: None,
17881        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17882            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17883            new_text: ".other".to_string(),
17884        })),
17885        ..lsp::CompletionItem::default()
17886    };
17887
17888    let item1 = item1.clone();
17889    cx.set_request_handler::<lsp::request::Completion, _, _>({
17890        let item1 = item1.clone();
17891        move |_, _, _| {
17892            let item1 = item1.clone();
17893            let item2 = item2.clone();
17894            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
17895        }
17896    })
17897    .next()
17898    .await;
17899
17900    cx.condition(|editor, _| editor.context_menu_visible())
17901        .await;
17902    cx.update_editor(|editor, _, _| {
17903        let context_menu = editor.context_menu.borrow_mut();
17904        let context_menu = context_menu
17905            .as_ref()
17906            .expect("Should have the context menu deployed");
17907        match context_menu {
17908            CodeContextMenu::Completions(completions_menu) => {
17909                let completions = completions_menu.completions.borrow_mut();
17910                assert_eq!(
17911                    completions
17912                        .iter()
17913                        .map(|completion| &completion.label.text)
17914                        .collect::<Vec<_>>(),
17915                    vec!["method id()", "other"]
17916                )
17917            }
17918            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17919        }
17920    });
17921
17922    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
17923        let item1 = item1.clone();
17924        move |_, item_to_resolve, _| {
17925            let item1 = item1.clone();
17926            async move {
17927                if item1 == item_to_resolve {
17928                    Ok(lsp::CompletionItem {
17929                        label: "method id()".to_string(),
17930                        filter_text: Some("id".to_string()),
17931                        detail: Some("Now resolved!".to_string()),
17932                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
17933                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17934                            range: lsp::Range::new(
17935                                lsp::Position::new(0, 22),
17936                                lsp::Position::new(0, 22),
17937                            ),
17938                            new_text: ".id".to_string(),
17939                        })),
17940                        ..lsp::CompletionItem::default()
17941                    })
17942                } else {
17943                    Ok(item_to_resolve)
17944                }
17945            }
17946        }
17947    })
17948    .next()
17949    .await
17950    .unwrap();
17951    cx.run_until_parked();
17952
17953    cx.update_editor(|editor, window, cx| {
17954        editor.context_menu_next(&Default::default(), window, cx);
17955    });
17956
17957    cx.update_editor(|editor, _, _| {
17958        let context_menu = editor.context_menu.borrow_mut();
17959        let context_menu = context_menu
17960            .as_ref()
17961            .expect("Should have the context menu deployed");
17962        match context_menu {
17963            CodeContextMenu::Completions(completions_menu) => {
17964                let completions = completions_menu.completions.borrow_mut();
17965                assert_eq!(
17966                    completions
17967                        .iter()
17968                        .map(|completion| &completion.label.text)
17969                        .collect::<Vec<_>>(),
17970                    vec!["method id() Now resolved!", "other"],
17971                    "Should update first completion label, but not second as the filter text did not match."
17972                );
17973            }
17974            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17975        }
17976    });
17977}
17978
17979#[gpui::test]
17980async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
17981    init_test(cx, |_| {});
17982    let mut cx = EditorLspTestContext::new_rust(
17983        lsp::ServerCapabilities {
17984            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
17985            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
17986            completion_provider: Some(lsp::CompletionOptions {
17987                resolve_provider: Some(true),
17988                ..Default::default()
17989            }),
17990            ..Default::default()
17991        },
17992        cx,
17993    )
17994    .await;
17995    cx.set_state(indoc! {"
17996        struct TestStruct {
17997            field: i32
17998        }
17999
18000        fn mainˇ() {
18001            let unused_var = 42;
18002            let test_struct = TestStruct { field: 42 };
18003        }
18004    "});
18005    let symbol_range = cx.lsp_range(indoc! {"
18006        struct TestStruct {
18007            field: i32
18008        }
18009
18010        «fn main»() {
18011            let unused_var = 42;
18012            let test_struct = TestStruct { field: 42 };
18013        }
18014    "});
18015    let mut hover_requests =
18016        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
18017            Ok(Some(lsp::Hover {
18018                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
18019                    kind: lsp::MarkupKind::Markdown,
18020                    value: "Function documentation".to_string(),
18021                }),
18022                range: Some(symbol_range),
18023            }))
18024        });
18025
18026    // Case 1: Test that code action menu hide hover popover
18027    cx.dispatch_action(Hover);
18028    hover_requests.next().await;
18029    cx.condition(|editor, _| editor.hover_state.visible()).await;
18030    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
18031        move |_, _, _| async move {
18032            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
18033                lsp::CodeAction {
18034                    title: "Remove unused variable".to_string(),
18035                    kind: Some(CodeActionKind::QUICKFIX),
18036                    edit: Some(lsp::WorkspaceEdit {
18037                        changes: Some(
18038                            [(
18039                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
18040                                vec![lsp::TextEdit {
18041                                    range: lsp::Range::new(
18042                                        lsp::Position::new(5, 4),
18043                                        lsp::Position::new(5, 27),
18044                                    ),
18045                                    new_text: "".to_string(),
18046                                }],
18047                            )]
18048                            .into_iter()
18049                            .collect(),
18050                        ),
18051                        ..Default::default()
18052                    }),
18053                    ..Default::default()
18054                },
18055            )]))
18056        },
18057    );
18058    cx.update_editor(|editor, window, cx| {
18059        editor.toggle_code_actions(
18060            &ToggleCodeActions {
18061                deployed_from: None,
18062                quick_launch: false,
18063            },
18064            window,
18065            cx,
18066        );
18067    });
18068    code_action_requests.next().await;
18069    cx.run_until_parked();
18070    cx.condition(|editor, _| editor.context_menu_visible())
18071        .await;
18072    cx.update_editor(|editor, _, _| {
18073        assert!(
18074            !editor.hover_state.visible(),
18075            "Hover popover should be hidden when code action menu is shown"
18076        );
18077        // Hide code actions
18078        editor.context_menu.take();
18079    });
18080
18081    // Case 2: Test that code completions hide hover popover
18082    cx.dispatch_action(Hover);
18083    hover_requests.next().await;
18084    cx.condition(|editor, _| editor.hover_state.visible()).await;
18085    let counter = Arc::new(AtomicUsize::new(0));
18086    let mut completion_requests =
18087        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18088            let counter = counter.clone();
18089            async move {
18090                counter.fetch_add(1, atomic::Ordering::Release);
18091                Ok(Some(lsp::CompletionResponse::Array(vec![
18092                    lsp::CompletionItem {
18093                        label: "main".into(),
18094                        kind: Some(lsp::CompletionItemKind::FUNCTION),
18095                        detail: Some("() -> ()".to_string()),
18096                        ..Default::default()
18097                    },
18098                    lsp::CompletionItem {
18099                        label: "TestStruct".into(),
18100                        kind: Some(lsp::CompletionItemKind::STRUCT),
18101                        detail: Some("struct TestStruct".to_string()),
18102                        ..Default::default()
18103                    },
18104                ])))
18105            }
18106        });
18107    cx.update_editor(|editor, window, cx| {
18108        editor.show_completions(&ShowCompletions, window, cx);
18109    });
18110    completion_requests.next().await;
18111    cx.condition(|editor, _| editor.context_menu_visible())
18112        .await;
18113    cx.update_editor(|editor, _, _| {
18114        assert!(
18115            !editor.hover_state.visible(),
18116            "Hover popover should be hidden when completion menu is shown"
18117        );
18118    });
18119}
18120
18121#[gpui::test]
18122async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
18123    init_test(cx, |_| {});
18124
18125    let mut cx = EditorLspTestContext::new_rust(
18126        lsp::ServerCapabilities {
18127            completion_provider: Some(lsp::CompletionOptions {
18128                trigger_characters: Some(vec![".".to_string()]),
18129                resolve_provider: Some(true),
18130                ..Default::default()
18131            }),
18132            ..Default::default()
18133        },
18134        cx,
18135    )
18136    .await;
18137
18138    cx.set_state("fn main() { let a = 2ˇ; }");
18139    cx.simulate_keystroke(".");
18140
18141    let unresolved_item_1 = lsp::CompletionItem {
18142        label: "id".to_string(),
18143        filter_text: Some("id".to_string()),
18144        detail: None,
18145        documentation: None,
18146        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18147            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18148            new_text: ".id".to_string(),
18149        })),
18150        ..lsp::CompletionItem::default()
18151    };
18152    let resolved_item_1 = lsp::CompletionItem {
18153        additional_text_edits: Some(vec![lsp::TextEdit {
18154            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
18155            new_text: "!!".to_string(),
18156        }]),
18157        ..unresolved_item_1.clone()
18158    };
18159    let unresolved_item_2 = lsp::CompletionItem {
18160        label: "other".to_string(),
18161        filter_text: Some("other".to_string()),
18162        detail: None,
18163        documentation: None,
18164        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18165            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18166            new_text: ".other".to_string(),
18167        })),
18168        ..lsp::CompletionItem::default()
18169    };
18170    let resolved_item_2 = lsp::CompletionItem {
18171        additional_text_edits: Some(vec![lsp::TextEdit {
18172            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
18173            new_text: "??".to_string(),
18174        }]),
18175        ..unresolved_item_2.clone()
18176    };
18177
18178    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
18179    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
18180    cx.lsp
18181        .server
18182        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
18183            let unresolved_item_1 = unresolved_item_1.clone();
18184            let resolved_item_1 = resolved_item_1.clone();
18185            let unresolved_item_2 = unresolved_item_2.clone();
18186            let resolved_item_2 = resolved_item_2.clone();
18187            let resolve_requests_1 = resolve_requests_1.clone();
18188            let resolve_requests_2 = resolve_requests_2.clone();
18189            move |unresolved_request, _| {
18190                let unresolved_item_1 = unresolved_item_1.clone();
18191                let resolved_item_1 = resolved_item_1.clone();
18192                let unresolved_item_2 = unresolved_item_2.clone();
18193                let resolved_item_2 = resolved_item_2.clone();
18194                let resolve_requests_1 = resolve_requests_1.clone();
18195                let resolve_requests_2 = resolve_requests_2.clone();
18196                async move {
18197                    if unresolved_request == unresolved_item_1 {
18198                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
18199                        Ok(resolved_item_1.clone())
18200                    } else if unresolved_request == unresolved_item_2 {
18201                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
18202                        Ok(resolved_item_2.clone())
18203                    } else {
18204                        panic!("Unexpected completion item {unresolved_request:?}")
18205                    }
18206                }
18207            }
18208        })
18209        .detach();
18210
18211    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18212        let unresolved_item_1 = unresolved_item_1.clone();
18213        let unresolved_item_2 = unresolved_item_2.clone();
18214        async move {
18215            Ok(Some(lsp::CompletionResponse::Array(vec![
18216                unresolved_item_1,
18217                unresolved_item_2,
18218            ])))
18219        }
18220    })
18221    .next()
18222    .await;
18223
18224    cx.condition(|editor, _| editor.context_menu_visible())
18225        .await;
18226    cx.update_editor(|editor, _, _| {
18227        let context_menu = editor.context_menu.borrow_mut();
18228        let context_menu = context_menu
18229            .as_ref()
18230            .expect("Should have the context menu deployed");
18231        match context_menu {
18232            CodeContextMenu::Completions(completions_menu) => {
18233                let completions = completions_menu.completions.borrow_mut();
18234                assert_eq!(
18235                    completions
18236                        .iter()
18237                        .map(|completion| &completion.label.text)
18238                        .collect::<Vec<_>>(),
18239                    vec!["id", "other"]
18240                )
18241            }
18242            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18243        }
18244    });
18245    cx.run_until_parked();
18246
18247    cx.update_editor(|editor, window, cx| {
18248        editor.context_menu_next(&ContextMenuNext, window, cx);
18249    });
18250    cx.run_until_parked();
18251    cx.update_editor(|editor, window, cx| {
18252        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
18253    });
18254    cx.run_until_parked();
18255    cx.update_editor(|editor, window, cx| {
18256        editor.context_menu_next(&ContextMenuNext, window, cx);
18257    });
18258    cx.run_until_parked();
18259    cx.update_editor(|editor, window, cx| {
18260        editor
18261            .compose_completion(&ComposeCompletion::default(), window, cx)
18262            .expect("No task returned")
18263    })
18264    .await
18265    .expect("Completion failed");
18266    cx.run_until_parked();
18267
18268    cx.update_editor(|editor, _, cx| {
18269        assert_eq!(
18270            resolve_requests_1.load(atomic::Ordering::Acquire),
18271            1,
18272            "Should always resolve once despite multiple selections"
18273        );
18274        assert_eq!(
18275            resolve_requests_2.load(atomic::Ordering::Acquire),
18276            1,
18277            "Should always resolve once after multiple selections and applying the completion"
18278        );
18279        assert_eq!(
18280            editor.text(cx),
18281            "fn main() { let a = ??.other; }",
18282            "Should use resolved data when applying the completion"
18283        );
18284    });
18285}
18286
18287#[gpui::test]
18288async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
18289    init_test(cx, |_| {});
18290
18291    let item_0 = lsp::CompletionItem {
18292        label: "abs".into(),
18293        insert_text: Some("abs".into()),
18294        data: Some(json!({ "very": "special"})),
18295        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
18296        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
18297            lsp::InsertReplaceEdit {
18298                new_text: "abs".to_string(),
18299                insert: lsp::Range::default(),
18300                replace: lsp::Range::default(),
18301            },
18302        )),
18303        ..lsp::CompletionItem::default()
18304    };
18305    let items = iter::once(item_0.clone())
18306        .chain((11..51).map(|i| lsp::CompletionItem {
18307            label: format!("item_{}", i),
18308            insert_text: Some(format!("item_{}", i)),
18309            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
18310            ..lsp::CompletionItem::default()
18311        }))
18312        .collect::<Vec<_>>();
18313
18314    let default_commit_characters = vec!["?".to_string()];
18315    let default_data = json!({ "default": "data"});
18316    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
18317    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
18318    let default_edit_range = lsp::Range {
18319        start: lsp::Position {
18320            line: 0,
18321            character: 5,
18322        },
18323        end: lsp::Position {
18324            line: 0,
18325            character: 5,
18326        },
18327    };
18328
18329    let mut cx = EditorLspTestContext::new_rust(
18330        lsp::ServerCapabilities {
18331            completion_provider: Some(lsp::CompletionOptions {
18332                trigger_characters: Some(vec![".".to_string()]),
18333                resolve_provider: Some(true),
18334                ..Default::default()
18335            }),
18336            ..Default::default()
18337        },
18338        cx,
18339    )
18340    .await;
18341
18342    cx.set_state("fn main() { let a = 2ˇ; }");
18343    cx.simulate_keystroke(".");
18344
18345    let completion_data = default_data.clone();
18346    let completion_characters = default_commit_characters.clone();
18347    let completion_items = items.clone();
18348    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18349        let default_data = completion_data.clone();
18350        let default_commit_characters = completion_characters.clone();
18351        let items = completion_items.clone();
18352        async move {
18353            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
18354                items,
18355                item_defaults: Some(lsp::CompletionListItemDefaults {
18356                    data: Some(default_data.clone()),
18357                    commit_characters: Some(default_commit_characters.clone()),
18358                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
18359                        default_edit_range,
18360                    )),
18361                    insert_text_format: Some(default_insert_text_format),
18362                    insert_text_mode: Some(default_insert_text_mode),
18363                }),
18364                ..lsp::CompletionList::default()
18365            })))
18366        }
18367    })
18368    .next()
18369    .await;
18370
18371    let resolved_items = Arc::new(Mutex::new(Vec::new()));
18372    cx.lsp
18373        .server
18374        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
18375            let closure_resolved_items = resolved_items.clone();
18376            move |item_to_resolve, _| {
18377                let closure_resolved_items = closure_resolved_items.clone();
18378                async move {
18379                    closure_resolved_items.lock().push(item_to_resolve.clone());
18380                    Ok(item_to_resolve)
18381                }
18382            }
18383        })
18384        .detach();
18385
18386    cx.condition(|editor, _| editor.context_menu_visible())
18387        .await;
18388    cx.run_until_parked();
18389    cx.update_editor(|editor, _, _| {
18390        let menu = editor.context_menu.borrow_mut();
18391        match menu.as_ref().expect("should have the completions menu") {
18392            CodeContextMenu::Completions(completions_menu) => {
18393                assert_eq!(
18394                    completions_menu
18395                        .entries
18396                        .borrow()
18397                        .iter()
18398                        .map(|mat| mat.string.clone())
18399                        .collect::<Vec<String>>(),
18400                    items
18401                        .iter()
18402                        .map(|completion| completion.label.clone())
18403                        .collect::<Vec<String>>()
18404                );
18405            }
18406            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
18407        }
18408    });
18409    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
18410    // with 4 from the end.
18411    assert_eq!(
18412        *resolved_items.lock(),
18413        [&items[0..16], &items[items.len() - 4..items.len()]]
18414            .concat()
18415            .iter()
18416            .cloned()
18417            .map(|mut item| {
18418                if item.data.is_none() {
18419                    item.data = Some(default_data.clone());
18420                }
18421                item
18422            })
18423            .collect::<Vec<lsp::CompletionItem>>(),
18424        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
18425    );
18426    resolved_items.lock().clear();
18427
18428    cx.update_editor(|editor, window, cx| {
18429        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
18430    });
18431    cx.run_until_parked();
18432    // Completions that have already been resolved are skipped.
18433    assert_eq!(
18434        *resolved_items.lock(),
18435        items[items.len() - 17..items.len() - 4]
18436            .iter()
18437            .cloned()
18438            .map(|mut item| {
18439                if item.data.is_none() {
18440                    item.data = Some(default_data.clone());
18441                }
18442                item
18443            })
18444            .collect::<Vec<lsp::CompletionItem>>()
18445    );
18446    resolved_items.lock().clear();
18447}
18448
18449#[gpui::test]
18450async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
18451    init_test(cx, |_| {});
18452
18453    let mut cx = EditorLspTestContext::new(
18454        Language::new(
18455            LanguageConfig {
18456                matcher: LanguageMatcher {
18457                    path_suffixes: vec!["jsx".into()],
18458                    ..Default::default()
18459                },
18460                overrides: [(
18461                    "element".into(),
18462                    LanguageConfigOverride {
18463                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
18464                        ..Default::default()
18465                    },
18466                )]
18467                .into_iter()
18468                .collect(),
18469                ..Default::default()
18470            },
18471            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
18472        )
18473        .with_override_query("(jsx_self_closing_element) @element")
18474        .unwrap(),
18475        lsp::ServerCapabilities {
18476            completion_provider: Some(lsp::CompletionOptions {
18477                trigger_characters: Some(vec![":".to_string()]),
18478                ..Default::default()
18479            }),
18480            ..Default::default()
18481        },
18482        cx,
18483    )
18484    .await;
18485
18486    cx.lsp
18487        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
18488            Ok(Some(lsp::CompletionResponse::Array(vec![
18489                lsp::CompletionItem {
18490                    label: "bg-blue".into(),
18491                    ..Default::default()
18492                },
18493                lsp::CompletionItem {
18494                    label: "bg-red".into(),
18495                    ..Default::default()
18496                },
18497                lsp::CompletionItem {
18498                    label: "bg-yellow".into(),
18499                    ..Default::default()
18500                },
18501            ])))
18502        });
18503
18504    cx.set_state(r#"<p class="bgˇ" />"#);
18505
18506    // Trigger completion when typing a dash, because the dash is an extra
18507    // word character in the 'element' scope, which contains the cursor.
18508    cx.simulate_keystroke("-");
18509    cx.executor().run_until_parked();
18510    cx.update_editor(|editor, _, _| {
18511        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18512        {
18513            assert_eq!(
18514                completion_menu_entries(menu),
18515                &["bg-blue", "bg-red", "bg-yellow"]
18516            );
18517        } else {
18518            panic!("expected completion menu to be open");
18519        }
18520    });
18521
18522    cx.simulate_keystroke("l");
18523    cx.executor().run_until_parked();
18524    cx.update_editor(|editor, _, _| {
18525        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18526        {
18527            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
18528        } else {
18529            panic!("expected completion menu to be open");
18530        }
18531    });
18532
18533    // When filtering completions, consider the character after the '-' to
18534    // be the start of a subword.
18535    cx.set_state(r#"<p class="yelˇ" />"#);
18536    cx.simulate_keystroke("l");
18537    cx.executor().run_until_parked();
18538    cx.update_editor(|editor, _, _| {
18539        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18540        {
18541            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
18542        } else {
18543            panic!("expected completion menu to be open");
18544        }
18545    });
18546}
18547
18548fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
18549    let entries = menu.entries.borrow();
18550    entries.iter().map(|mat| mat.string.clone()).collect()
18551}
18552
18553#[gpui::test]
18554async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
18555    init_test(cx, |settings| {
18556        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
18557    });
18558
18559    let fs = FakeFs::new(cx.executor());
18560    fs.insert_file(path!("/file.ts"), Default::default()).await;
18561
18562    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
18563    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18564
18565    language_registry.add(Arc::new(Language::new(
18566        LanguageConfig {
18567            name: "TypeScript".into(),
18568            matcher: LanguageMatcher {
18569                path_suffixes: vec!["ts".to_string()],
18570                ..Default::default()
18571            },
18572            ..Default::default()
18573        },
18574        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
18575    )));
18576    update_test_language_settings(cx, |settings| {
18577        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
18578    });
18579
18580    let test_plugin = "test_plugin";
18581    let _ = language_registry.register_fake_lsp(
18582        "TypeScript",
18583        FakeLspAdapter {
18584            prettier_plugins: vec![test_plugin],
18585            ..Default::default()
18586        },
18587    );
18588
18589    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
18590    let buffer = project
18591        .update(cx, |project, cx| {
18592            project.open_local_buffer(path!("/file.ts"), cx)
18593        })
18594        .await
18595        .unwrap();
18596
18597    let buffer_text = "one\ntwo\nthree\n";
18598    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
18599    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
18600    editor.update_in(cx, |editor, window, cx| {
18601        editor.set_text(buffer_text, window, cx)
18602    });
18603
18604    editor
18605        .update_in(cx, |editor, window, cx| {
18606            editor.perform_format(
18607                project.clone(),
18608                FormatTrigger::Manual,
18609                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18610                window,
18611                cx,
18612            )
18613        })
18614        .unwrap()
18615        .await;
18616    assert_eq!(
18617        editor.update(cx, |editor, cx| editor.text(cx)),
18618        buffer_text.to_string() + prettier_format_suffix,
18619        "Test prettier formatting was not applied to the original buffer text",
18620    );
18621
18622    update_test_language_settings(cx, |settings| {
18623        settings.defaults.formatter = Some(FormatterList::default())
18624    });
18625    let format = editor.update_in(cx, |editor, window, cx| {
18626        editor.perform_format(
18627            project.clone(),
18628            FormatTrigger::Manual,
18629            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18630            window,
18631            cx,
18632        )
18633    });
18634    format.await.unwrap();
18635    assert_eq!(
18636        editor.update(cx, |editor, cx| editor.text(cx)),
18637        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
18638        "Autoformatting (via test prettier) was not applied to the original buffer text",
18639    );
18640}
18641
18642#[gpui::test]
18643async fn test_addition_reverts(cx: &mut TestAppContext) {
18644    init_test(cx, |_| {});
18645    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18646    let base_text = indoc! {r#"
18647        struct Row;
18648        struct Row1;
18649        struct Row2;
18650
18651        struct Row4;
18652        struct Row5;
18653        struct Row6;
18654
18655        struct Row8;
18656        struct Row9;
18657        struct Row10;"#};
18658
18659    // When addition hunks are not adjacent to carets, no hunk revert is performed
18660    assert_hunk_revert(
18661        indoc! {r#"struct Row;
18662                   struct Row1;
18663                   struct Row1.1;
18664                   struct Row1.2;
18665                   struct Row2;ˇ
18666
18667                   struct Row4;
18668                   struct Row5;
18669                   struct Row6;
18670
18671                   struct Row8;
18672                   ˇstruct Row9;
18673                   struct Row9.1;
18674                   struct Row9.2;
18675                   struct Row9.3;
18676                   struct Row10;"#},
18677        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18678        indoc! {r#"struct Row;
18679                   struct Row1;
18680                   struct Row1.1;
18681                   struct Row1.2;
18682                   struct Row2;ˇ
18683
18684                   struct Row4;
18685                   struct Row5;
18686                   struct Row6;
18687
18688                   struct Row8;
18689                   ˇstruct Row9;
18690                   struct Row9.1;
18691                   struct Row9.2;
18692                   struct Row9.3;
18693                   struct Row10;"#},
18694        base_text,
18695        &mut cx,
18696    );
18697    // Same for selections
18698    assert_hunk_revert(
18699        indoc! {r#"struct Row;
18700                   struct Row1;
18701                   struct Row2;
18702                   struct Row2.1;
18703                   struct Row2.2;
18704                   «ˇ
18705                   struct Row4;
18706                   struct» Row5;
18707                   «struct Row6;
18708                   ˇ»
18709                   struct Row9.1;
18710                   struct Row9.2;
18711                   struct Row9.3;
18712                   struct Row8;
18713                   struct Row9;
18714                   struct Row10;"#},
18715        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18716        indoc! {r#"struct Row;
18717                   struct Row1;
18718                   struct Row2;
18719                   struct Row2.1;
18720                   struct Row2.2;
18721                   «ˇ
18722                   struct Row4;
18723                   struct» Row5;
18724                   «struct Row6;
18725                   ˇ»
18726                   struct Row9.1;
18727                   struct Row9.2;
18728                   struct Row9.3;
18729                   struct Row8;
18730                   struct Row9;
18731                   struct Row10;"#},
18732        base_text,
18733        &mut cx,
18734    );
18735
18736    // When carets and selections intersect the addition hunks, those are reverted.
18737    // Adjacent carets got merged.
18738    assert_hunk_revert(
18739        indoc! {r#"struct Row;
18740                   ˇ// something on the top
18741                   struct Row1;
18742                   struct Row2;
18743                   struct Roˇw3.1;
18744                   struct Row2.2;
18745                   struct Row2.3;ˇ
18746
18747                   struct Row4;
18748                   struct ˇRow5.1;
18749                   struct Row5.2;
18750                   struct «Rowˇ»5.3;
18751                   struct Row5;
18752                   struct Row6;
18753                   ˇ
18754                   struct Row9.1;
18755                   struct «Rowˇ»9.2;
18756                   struct «ˇRow»9.3;
18757                   struct Row8;
18758                   struct Row9;
18759                   «ˇ// something on bottom»
18760                   struct Row10;"#},
18761        vec![
18762            DiffHunkStatusKind::Added,
18763            DiffHunkStatusKind::Added,
18764            DiffHunkStatusKind::Added,
18765            DiffHunkStatusKind::Added,
18766            DiffHunkStatusKind::Added,
18767        ],
18768        indoc! {r#"struct Row;
18769                   ˇstruct Row1;
18770                   struct Row2;
18771                   ˇ
18772                   struct Row4;
18773                   ˇstruct Row5;
18774                   struct Row6;
18775                   ˇ
18776                   ˇstruct Row8;
18777                   struct Row9;
18778                   ˇstruct Row10;"#},
18779        base_text,
18780        &mut cx,
18781    );
18782}
18783
18784#[gpui::test]
18785async fn test_modification_reverts(cx: &mut TestAppContext) {
18786    init_test(cx, |_| {});
18787    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18788    let base_text = indoc! {r#"
18789        struct Row;
18790        struct Row1;
18791        struct Row2;
18792
18793        struct Row4;
18794        struct Row5;
18795        struct Row6;
18796
18797        struct Row8;
18798        struct Row9;
18799        struct Row10;"#};
18800
18801    // Modification hunks behave the same as the addition ones.
18802    assert_hunk_revert(
18803        indoc! {r#"struct Row;
18804                   struct Row1;
18805                   struct Row33;
18806                   ˇ
18807                   struct Row4;
18808                   struct Row5;
18809                   struct Row6;
18810                   ˇ
18811                   struct Row99;
18812                   struct Row9;
18813                   struct Row10;"#},
18814        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18815        indoc! {r#"struct Row;
18816                   struct Row1;
18817                   struct Row33;
18818                   ˇ
18819                   struct Row4;
18820                   struct Row5;
18821                   struct Row6;
18822                   ˇ
18823                   struct Row99;
18824                   struct Row9;
18825                   struct Row10;"#},
18826        base_text,
18827        &mut cx,
18828    );
18829    assert_hunk_revert(
18830        indoc! {r#"struct Row;
18831                   struct Row1;
18832                   struct Row33;
18833                   «ˇ
18834                   struct Row4;
18835                   struct» Row5;
18836                   «struct Row6;
18837                   ˇ»
18838                   struct Row99;
18839                   struct Row9;
18840                   struct Row10;"#},
18841        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18842        indoc! {r#"struct Row;
18843                   struct Row1;
18844                   struct Row33;
18845                   «ˇ
18846                   struct Row4;
18847                   struct» Row5;
18848                   «struct Row6;
18849                   ˇ»
18850                   struct Row99;
18851                   struct Row9;
18852                   struct Row10;"#},
18853        base_text,
18854        &mut cx,
18855    );
18856
18857    assert_hunk_revert(
18858        indoc! {r#"ˇstruct Row1.1;
18859                   struct Row1;
18860                   «ˇstr»uct Row22;
18861
18862                   struct ˇRow44;
18863                   struct Row5;
18864                   struct «Rˇ»ow66;ˇ
18865
18866                   «struˇ»ct Row88;
18867                   struct Row9;
18868                   struct Row1011;ˇ"#},
18869        vec![
18870            DiffHunkStatusKind::Modified,
18871            DiffHunkStatusKind::Modified,
18872            DiffHunkStatusKind::Modified,
18873            DiffHunkStatusKind::Modified,
18874            DiffHunkStatusKind::Modified,
18875            DiffHunkStatusKind::Modified,
18876        ],
18877        indoc! {r#"struct Row;
18878                   ˇstruct Row1;
18879                   struct Row2;
18880                   ˇ
18881                   struct Row4;
18882                   ˇstruct Row5;
18883                   struct Row6;
18884                   ˇ
18885                   struct Row8;
18886                   ˇstruct Row9;
18887                   struct Row10;ˇ"#},
18888        base_text,
18889        &mut cx,
18890    );
18891}
18892
18893#[gpui::test]
18894async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
18895    init_test(cx, |_| {});
18896    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18897    let base_text = indoc! {r#"
18898        one
18899
18900        two
18901        three
18902        "#};
18903
18904    cx.set_head_text(base_text);
18905    cx.set_state("\nˇ\n");
18906    cx.executor().run_until_parked();
18907    cx.update_editor(|editor, _window, cx| {
18908        editor.expand_selected_diff_hunks(cx);
18909    });
18910    cx.executor().run_until_parked();
18911    cx.update_editor(|editor, window, cx| {
18912        editor.backspace(&Default::default(), window, cx);
18913    });
18914    cx.run_until_parked();
18915    cx.assert_state_with_diff(
18916        indoc! {r#"
18917
18918        - two
18919        - threeˇ
18920        +
18921        "#}
18922        .to_string(),
18923    );
18924}
18925
18926#[gpui::test]
18927async fn test_deletion_reverts(cx: &mut TestAppContext) {
18928    init_test(cx, |_| {});
18929    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18930    let base_text = indoc! {r#"struct Row;
18931struct Row1;
18932struct Row2;
18933
18934struct Row4;
18935struct Row5;
18936struct Row6;
18937
18938struct Row8;
18939struct Row9;
18940struct Row10;"#};
18941
18942    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
18943    assert_hunk_revert(
18944        indoc! {r#"struct Row;
18945                   struct Row2;
18946
18947                   ˇstruct Row4;
18948                   struct Row5;
18949                   struct Row6;
18950                   ˇ
18951                   struct Row8;
18952                   struct Row10;"#},
18953        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18954        indoc! {r#"struct Row;
18955                   struct Row2;
18956
18957                   ˇstruct Row4;
18958                   struct Row5;
18959                   struct Row6;
18960                   ˇ
18961                   struct Row8;
18962                   struct Row10;"#},
18963        base_text,
18964        &mut cx,
18965    );
18966    assert_hunk_revert(
18967        indoc! {r#"struct Row;
18968                   struct Row2;
18969
18970                   «ˇstruct Row4;
18971                   struct» Row5;
18972                   «struct Row6;
18973                   ˇ»
18974                   struct Row8;
18975                   struct Row10;"#},
18976        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18977        indoc! {r#"struct Row;
18978                   struct Row2;
18979
18980                   «ˇstruct Row4;
18981                   struct» Row5;
18982                   «struct Row6;
18983                   ˇ»
18984                   struct Row8;
18985                   struct Row10;"#},
18986        base_text,
18987        &mut cx,
18988    );
18989
18990    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
18991    assert_hunk_revert(
18992        indoc! {r#"struct Row;
18993                   ˇstruct Row2;
18994
18995                   struct Row4;
18996                   struct Row5;
18997                   struct Row6;
18998
18999                   struct Row8;ˇ
19000                   struct Row10;"#},
19001        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
19002        indoc! {r#"struct Row;
19003                   struct Row1;
19004                   ˇstruct Row2;
19005
19006                   struct Row4;
19007                   struct Row5;
19008                   struct Row6;
19009
19010                   struct Row8;ˇ
19011                   struct Row9;
19012                   struct Row10;"#},
19013        base_text,
19014        &mut cx,
19015    );
19016    assert_hunk_revert(
19017        indoc! {r#"struct Row;
19018                   struct Row2«ˇ;
19019                   struct Row4;
19020                   struct» Row5;
19021                   «struct Row6;
19022
19023                   struct Row8;ˇ»
19024                   struct Row10;"#},
19025        vec![
19026            DiffHunkStatusKind::Deleted,
19027            DiffHunkStatusKind::Deleted,
19028            DiffHunkStatusKind::Deleted,
19029        ],
19030        indoc! {r#"struct Row;
19031                   struct Row1;
19032                   struct Row2«ˇ;
19033
19034                   struct Row4;
19035                   struct» Row5;
19036                   «struct Row6;
19037
19038                   struct Row8;ˇ»
19039                   struct Row9;
19040                   struct Row10;"#},
19041        base_text,
19042        &mut cx,
19043    );
19044}
19045
19046#[gpui::test]
19047async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
19048    init_test(cx, |_| {});
19049
19050    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
19051    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
19052    let base_text_3 =
19053        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
19054
19055    let text_1 = edit_first_char_of_every_line(base_text_1);
19056    let text_2 = edit_first_char_of_every_line(base_text_2);
19057    let text_3 = edit_first_char_of_every_line(base_text_3);
19058
19059    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
19060    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
19061    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
19062
19063    let multibuffer = cx.new(|cx| {
19064        let mut multibuffer = MultiBuffer::new(ReadWrite);
19065        multibuffer.push_excerpts(
19066            buffer_1.clone(),
19067            [
19068                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19069                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19070                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19071            ],
19072            cx,
19073        );
19074        multibuffer.push_excerpts(
19075            buffer_2.clone(),
19076            [
19077                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19078                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19079                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19080            ],
19081            cx,
19082        );
19083        multibuffer.push_excerpts(
19084            buffer_3.clone(),
19085            [
19086                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19087                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19088                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19089            ],
19090            cx,
19091        );
19092        multibuffer
19093    });
19094
19095    let fs = FakeFs::new(cx.executor());
19096    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
19097    let (editor, cx) = cx
19098        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
19099    editor.update_in(cx, |editor, _window, cx| {
19100        for (buffer, diff_base) in [
19101            (buffer_1.clone(), base_text_1),
19102            (buffer_2.clone(), base_text_2),
19103            (buffer_3.clone(), base_text_3),
19104        ] {
19105            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
19106            editor
19107                .buffer
19108                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
19109        }
19110    });
19111    cx.executor().run_until_parked();
19112
19113    editor.update_in(cx, |editor, window, cx| {
19114        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}");
19115        editor.select_all(&SelectAll, window, cx);
19116        editor.git_restore(&Default::default(), window, cx);
19117    });
19118    cx.executor().run_until_parked();
19119
19120    // When all ranges are selected, all buffer hunks are reverted.
19121    editor.update(cx, |editor, cx| {
19122        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");
19123    });
19124    buffer_1.update(cx, |buffer, _| {
19125        assert_eq!(buffer.text(), base_text_1);
19126    });
19127    buffer_2.update(cx, |buffer, _| {
19128        assert_eq!(buffer.text(), base_text_2);
19129    });
19130    buffer_3.update(cx, |buffer, _| {
19131        assert_eq!(buffer.text(), base_text_3);
19132    });
19133
19134    editor.update_in(cx, |editor, window, cx| {
19135        editor.undo(&Default::default(), window, cx);
19136    });
19137
19138    editor.update_in(cx, |editor, window, cx| {
19139        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19140            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
19141        });
19142        editor.git_restore(&Default::default(), window, cx);
19143    });
19144
19145    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
19146    // but not affect buffer_2 and its related excerpts.
19147    editor.update(cx, |editor, cx| {
19148        assert_eq!(
19149            editor.text(cx),
19150            "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}"
19151        );
19152    });
19153    buffer_1.update(cx, |buffer, _| {
19154        assert_eq!(buffer.text(), base_text_1);
19155    });
19156    buffer_2.update(cx, |buffer, _| {
19157        assert_eq!(
19158            buffer.text(),
19159            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
19160        );
19161    });
19162    buffer_3.update(cx, |buffer, _| {
19163        assert_eq!(
19164            buffer.text(),
19165            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
19166        );
19167    });
19168
19169    fn edit_first_char_of_every_line(text: &str) -> String {
19170        text.split('\n')
19171            .map(|line| format!("X{}", &line[1..]))
19172            .collect::<Vec<_>>()
19173            .join("\n")
19174    }
19175}
19176
19177#[gpui::test]
19178async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
19179    init_test(cx, |_| {});
19180
19181    let cols = 4;
19182    let rows = 10;
19183    let sample_text_1 = sample_text(rows, cols, 'a');
19184    assert_eq!(
19185        sample_text_1,
19186        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
19187    );
19188    let sample_text_2 = sample_text(rows, cols, 'l');
19189    assert_eq!(
19190        sample_text_2,
19191        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
19192    );
19193    let sample_text_3 = sample_text(rows, cols, 'v');
19194    assert_eq!(
19195        sample_text_3,
19196        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
19197    );
19198
19199    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
19200    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
19201    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
19202
19203    let multi_buffer = cx.new(|cx| {
19204        let mut multibuffer = MultiBuffer::new(ReadWrite);
19205        multibuffer.push_excerpts(
19206            buffer_1.clone(),
19207            [
19208                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19209                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19210                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19211            ],
19212            cx,
19213        );
19214        multibuffer.push_excerpts(
19215            buffer_2.clone(),
19216            [
19217                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19218                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19219                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19220            ],
19221            cx,
19222        );
19223        multibuffer.push_excerpts(
19224            buffer_3.clone(),
19225            [
19226                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19227                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19228                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19229            ],
19230            cx,
19231        );
19232        multibuffer
19233    });
19234
19235    let fs = FakeFs::new(cx.executor());
19236    fs.insert_tree(
19237        "/a",
19238        json!({
19239            "main.rs": sample_text_1,
19240            "other.rs": sample_text_2,
19241            "lib.rs": sample_text_3,
19242        }),
19243    )
19244    .await;
19245    let project = Project::test(fs, ["/a".as_ref()], cx).await;
19246    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19247    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19248    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19249        Editor::new(
19250            EditorMode::full(),
19251            multi_buffer,
19252            Some(project.clone()),
19253            window,
19254            cx,
19255        )
19256    });
19257    let multibuffer_item_id = workspace
19258        .update(cx, |workspace, window, cx| {
19259            assert!(
19260                workspace.active_item(cx).is_none(),
19261                "active item should be None before the first item is added"
19262            );
19263            workspace.add_item_to_active_pane(
19264                Box::new(multi_buffer_editor.clone()),
19265                None,
19266                true,
19267                window,
19268                cx,
19269            );
19270            let active_item = workspace
19271                .active_item(cx)
19272                .expect("should have an active item after adding the multi buffer");
19273            assert_eq!(
19274                active_item.buffer_kind(cx),
19275                ItemBufferKind::Multibuffer,
19276                "A multi buffer was expected to active after adding"
19277            );
19278            active_item.item_id()
19279        })
19280        .unwrap();
19281    cx.executor().run_until_parked();
19282
19283    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19284        editor.change_selections(
19285            SelectionEffects::scroll(Autoscroll::Next),
19286            window,
19287            cx,
19288            |s| s.select_ranges(Some(1..2)),
19289        );
19290        editor.open_excerpts(&OpenExcerpts, window, cx);
19291    });
19292    cx.executor().run_until_parked();
19293    let first_item_id = workspace
19294        .update(cx, |workspace, window, cx| {
19295            let active_item = workspace
19296                .active_item(cx)
19297                .expect("should have an active item after navigating into the 1st buffer");
19298            let first_item_id = active_item.item_id();
19299            assert_ne!(
19300                first_item_id, multibuffer_item_id,
19301                "Should navigate into the 1st buffer and activate it"
19302            );
19303            assert_eq!(
19304                active_item.buffer_kind(cx),
19305                ItemBufferKind::Singleton,
19306                "New active item should be a singleton buffer"
19307            );
19308            assert_eq!(
19309                active_item
19310                    .act_as::<Editor>(cx)
19311                    .expect("should have navigated into an editor for the 1st buffer")
19312                    .read(cx)
19313                    .text(cx),
19314                sample_text_1
19315            );
19316
19317            workspace
19318                .go_back(workspace.active_pane().downgrade(), window, cx)
19319                .detach_and_log_err(cx);
19320
19321            first_item_id
19322        })
19323        .unwrap();
19324    cx.executor().run_until_parked();
19325    workspace
19326        .update(cx, |workspace, _, cx| {
19327            let active_item = workspace
19328                .active_item(cx)
19329                .expect("should have an active item after navigating back");
19330            assert_eq!(
19331                active_item.item_id(),
19332                multibuffer_item_id,
19333                "Should navigate back to the multi buffer"
19334            );
19335            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19336        })
19337        .unwrap();
19338
19339    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19340        editor.change_selections(
19341            SelectionEffects::scroll(Autoscroll::Next),
19342            window,
19343            cx,
19344            |s| s.select_ranges(Some(39..40)),
19345        );
19346        editor.open_excerpts(&OpenExcerpts, window, cx);
19347    });
19348    cx.executor().run_until_parked();
19349    let second_item_id = workspace
19350        .update(cx, |workspace, window, cx| {
19351            let active_item = workspace
19352                .active_item(cx)
19353                .expect("should have an active item after navigating into the 2nd buffer");
19354            let second_item_id = active_item.item_id();
19355            assert_ne!(
19356                second_item_id, multibuffer_item_id,
19357                "Should navigate away from the multibuffer"
19358            );
19359            assert_ne!(
19360                second_item_id, first_item_id,
19361                "Should navigate into the 2nd buffer and activate it"
19362            );
19363            assert_eq!(
19364                active_item.buffer_kind(cx),
19365                ItemBufferKind::Singleton,
19366                "New active item should be a singleton buffer"
19367            );
19368            assert_eq!(
19369                active_item
19370                    .act_as::<Editor>(cx)
19371                    .expect("should have navigated into an editor")
19372                    .read(cx)
19373                    .text(cx),
19374                sample_text_2
19375            );
19376
19377            workspace
19378                .go_back(workspace.active_pane().downgrade(), window, cx)
19379                .detach_and_log_err(cx);
19380
19381            second_item_id
19382        })
19383        .unwrap();
19384    cx.executor().run_until_parked();
19385    workspace
19386        .update(cx, |workspace, _, cx| {
19387            let active_item = workspace
19388                .active_item(cx)
19389                .expect("should have an active item after navigating back from the 2nd buffer");
19390            assert_eq!(
19391                active_item.item_id(),
19392                multibuffer_item_id,
19393                "Should navigate back from the 2nd buffer to the multi buffer"
19394            );
19395            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19396        })
19397        .unwrap();
19398
19399    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19400        editor.change_selections(
19401            SelectionEffects::scroll(Autoscroll::Next),
19402            window,
19403            cx,
19404            |s| s.select_ranges(Some(70..70)),
19405        );
19406        editor.open_excerpts(&OpenExcerpts, window, cx);
19407    });
19408    cx.executor().run_until_parked();
19409    workspace
19410        .update(cx, |workspace, window, cx| {
19411            let active_item = workspace
19412                .active_item(cx)
19413                .expect("should have an active item after navigating into the 3rd buffer");
19414            let third_item_id = active_item.item_id();
19415            assert_ne!(
19416                third_item_id, multibuffer_item_id,
19417                "Should navigate into the 3rd buffer and activate it"
19418            );
19419            assert_ne!(third_item_id, first_item_id);
19420            assert_ne!(third_item_id, second_item_id);
19421            assert_eq!(
19422                active_item.buffer_kind(cx),
19423                ItemBufferKind::Singleton,
19424                "New active item should be a singleton buffer"
19425            );
19426            assert_eq!(
19427                active_item
19428                    .act_as::<Editor>(cx)
19429                    .expect("should have navigated into an editor")
19430                    .read(cx)
19431                    .text(cx),
19432                sample_text_3
19433            );
19434
19435            workspace
19436                .go_back(workspace.active_pane().downgrade(), window, cx)
19437                .detach_and_log_err(cx);
19438        })
19439        .unwrap();
19440    cx.executor().run_until_parked();
19441    workspace
19442        .update(cx, |workspace, _, cx| {
19443            let active_item = workspace
19444                .active_item(cx)
19445                .expect("should have an active item after navigating back from the 3rd buffer");
19446            assert_eq!(
19447                active_item.item_id(),
19448                multibuffer_item_id,
19449                "Should navigate back from the 3rd buffer to the multi buffer"
19450            );
19451            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19452        })
19453        .unwrap();
19454}
19455
19456#[gpui::test]
19457async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19458    init_test(cx, |_| {});
19459
19460    let mut cx = EditorTestContext::new(cx).await;
19461
19462    let diff_base = r#"
19463        use some::mod;
19464
19465        const A: u32 = 42;
19466
19467        fn main() {
19468            println!("hello");
19469
19470            println!("world");
19471        }
19472        "#
19473    .unindent();
19474
19475    cx.set_state(
19476        &r#"
19477        use some::modified;
19478
19479        ˇ
19480        fn main() {
19481            println!("hello there");
19482
19483            println!("around the");
19484            println!("world");
19485        }
19486        "#
19487        .unindent(),
19488    );
19489
19490    cx.set_head_text(&diff_base);
19491    executor.run_until_parked();
19492
19493    cx.update_editor(|editor, window, cx| {
19494        editor.go_to_next_hunk(&GoToHunk, window, cx);
19495        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19496    });
19497    executor.run_until_parked();
19498    cx.assert_state_with_diff(
19499        r#"
19500          use some::modified;
19501
19502
19503          fn main() {
19504        -     println!("hello");
19505        + ˇ    println!("hello there");
19506
19507              println!("around the");
19508              println!("world");
19509          }
19510        "#
19511        .unindent(),
19512    );
19513
19514    cx.update_editor(|editor, window, cx| {
19515        for _ in 0..2 {
19516            editor.go_to_next_hunk(&GoToHunk, window, cx);
19517            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19518        }
19519    });
19520    executor.run_until_parked();
19521    cx.assert_state_with_diff(
19522        r#"
19523        - use some::mod;
19524        + ˇuse some::modified;
19525
19526
19527          fn main() {
19528        -     println!("hello");
19529        +     println!("hello there");
19530
19531        +     println!("around the");
19532              println!("world");
19533          }
19534        "#
19535        .unindent(),
19536    );
19537
19538    cx.update_editor(|editor, window, cx| {
19539        editor.go_to_next_hunk(&GoToHunk, window, cx);
19540        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19541    });
19542    executor.run_until_parked();
19543    cx.assert_state_with_diff(
19544        r#"
19545        - use some::mod;
19546        + use some::modified;
19547
19548        - const A: u32 = 42;
19549          ˇ
19550          fn main() {
19551        -     println!("hello");
19552        +     println!("hello there");
19553
19554        +     println!("around the");
19555              println!("world");
19556          }
19557        "#
19558        .unindent(),
19559    );
19560
19561    cx.update_editor(|editor, window, cx| {
19562        editor.cancel(&Cancel, window, cx);
19563    });
19564
19565    cx.assert_state_with_diff(
19566        r#"
19567          use some::modified;
19568
19569          ˇ
19570          fn main() {
19571              println!("hello there");
19572
19573              println!("around the");
19574              println!("world");
19575          }
19576        "#
19577        .unindent(),
19578    );
19579}
19580
19581#[gpui::test]
19582async fn test_diff_base_change_with_expanded_diff_hunks(
19583    executor: BackgroundExecutor,
19584    cx: &mut TestAppContext,
19585) {
19586    init_test(cx, |_| {});
19587
19588    let mut cx = EditorTestContext::new(cx).await;
19589
19590    let diff_base = r#"
19591        use some::mod1;
19592        use some::mod2;
19593
19594        const A: u32 = 42;
19595        const B: u32 = 42;
19596        const C: u32 = 42;
19597
19598        fn main() {
19599            println!("hello");
19600
19601            println!("world");
19602        }
19603        "#
19604    .unindent();
19605
19606    cx.set_state(
19607        &r#"
19608        use some::mod2;
19609
19610        const A: u32 = 42;
19611        const C: u32 = 42;
19612
19613        fn main(ˇ) {
19614            //println!("hello");
19615
19616            println!("world");
19617            //
19618            //
19619        }
19620        "#
19621        .unindent(),
19622    );
19623
19624    cx.set_head_text(&diff_base);
19625    executor.run_until_parked();
19626
19627    cx.update_editor(|editor, window, cx| {
19628        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19629    });
19630    executor.run_until_parked();
19631    cx.assert_state_with_diff(
19632        r#"
19633        - use some::mod1;
19634          use some::mod2;
19635
19636          const A: u32 = 42;
19637        - const B: u32 = 42;
19638          const C: u32 = 42;
19639
19640          fn main(ˇ) {
19641        -     println!("hello");
19642        +     //println!("hello");
19643
19644              println!("world");
19645        +     //
19646        +     //
19647          }
19648        "#
19649        .unindent(),
19650    );
19651
19652    cx.set_head_text("new diff base!");
19653    executor.run_until_parked();
19654    cx.assert_state_with_diff(
19655        r#"
19656        - new diff base!
19657        + use some::mod2;
19658        +
19659        + const A: u32 = 42;
19660        + const C: u32 = 42;
19661        +
19662        + fn main(ˇ) {
19663        +     //println!("hello");
19664        +
19665        +     println!("world");
19666        +     //
19667        +     //
19668        + }
19669        "#
19670        .unindent(),
19671    );
19672}
19673
19674#[gpui::test]
19675async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
19676    init_test(cx, |_| {});
19677
19678    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19679    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19680    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19681    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19682    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
19683    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
19684
19685    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
19686    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
19687    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
19688
19689    let multi_buffer = cx.new(|cx| {
19690        let mut multibuffer = MultiBuffer::new(ReadWrite);
19691        multibuffer.push_excerpts(
19692            buffer_1.clone(),
19693            [
19694                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19695                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19696                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19697            ],
19698            cx,
19699        );
19700        multibuffer.push_excerpts(
19701            buffer_2.clone(),
19702            [
19703                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19704                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19705                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19706            ],
19707            cx,
19708        );
19709        multibuffer.push_excerpts(
19710            buffer_3.clone(),
19711            [
19712                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19713                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19714                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19715            ],
19716            cx,
19717        );
19718        multibuffer
19719    });
19720
19721    let editor =
19722        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19723    editor
19724        .update(cx, |editor, _window, cx| {
19725            for (buffer, diff_base) in [
19726                (buffer_1.clone(), file_1_old),
19727                (buffer_2.clone(), file_2_old),
19728                (buffer_3.clone(), file_3_old),
19729            ] {
19730                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
19731                editor
19732                    .buffer
19733                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
19734            }
19735        })
19736        .unwrap();
19737
19738    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19739    cx.run_until_parked();
19740
19741    cx.assert_editor_state(
19742        &"
19743            ˇaaa
19744            ccc
19745            ddd
19746
19747            ggg
19748            hhh
19749
19750
19751            lll
19752            mmm
19753            NNN
19754
19755            qqq
19756            rrr
19757
19758            uuu
19759            111
19760            222
19761            333
19762
19763            666
19764            777
19765
19766            000
19767            !!!"
19768        .unindent(),
19769    );
19770
19771    cx.update_editor(|editor, window, cx| {
19772        editor.select_all(&SelectAll, window, cx);
19773        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19774    });
19775    cx.executor().run_until_parked();
19776
19777    cx.assert_state_with_diff(
19778        "
19779            «aaa
19780          - bbb
19781            ccc
19782            ddd
19783
19784            ggg
19785            hhh
19786
19787
19788            lll
19789            mmm
19790          - nnn
19791          + NNN
19792
19793            qqq
19794            rrr
19795
19796            uuu
19797            111
19798            222
19799            333
19800
19801          + 666
19802            777
19803
19804            000
19805            !!!ˇ»"
19806            .unindent(),
19807    );
19808}
19809
19810#[gpui::test]
19811async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
19812    init_test(cx, |_| {});
19813
19814    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
19815    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
19816
19817    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
19818    let multi_buffer = cx.new(|cx| {
19819        let mut multibuffer = MultiBuffer::new(ReadWrite);
19820        multibuffer.push_excerpts(
19821            buffer.clone(),
19822            [
19823                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
19824                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
19825                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
19826            ],
19827            cx,
19828        );
19829        multibuffer
19830    });
19831
19832    let editor =
19833        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19834    editor
19835        .update(cx, |editor, _window, cx| {
19836            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
19837            editor
19838                .buffer
19839                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
19840        })
19841        .unwrap();
19842
19843    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19844    cx.run_until_parked();
19845
19846    cx.update_editor(|editor, window, cx| {
19847        editor.expand_all_diff_hunks(&Default::default(), window, cx)
19848    });
19849    cx.executor().run_until_parked();
19850
19851    // When the start of a hunk coincides with the start of its excerpt,
19852    // the hunk is expanded. When the start of a hunk is earlier than
19853    // the start of its excerpt, the hunk is not expanded.
19854    cx.assert_state_with_diff(
19855        "
19856            ˇaaa
19857          - bbb
19858          + BBB
19859
19860          - ddd
19861          - eee
19862          + DDD
19863          + EEE
19864            fff
19865
19866            iii
19867        "
19868        .unindent(),
19869    );
19870}
19871
19872#[gpui::test]
19873async fn test_edits_around_expanded_insertion_hunks(
19874    executor: BackgroundExecutor,
19875    cx: &mut TestAppContext,
19876) {
19877    init_test(cx, |_| {});
19878
19879    let mut cx = EditorTestContext::new(cx).await;
19880
19881    let diff_base = r#"
19882        use some::mod1;
19883        use some::mod2;
19884
19885        const A: u32 = 42;
19886
19887        fn main() {
19888            println!("hello");
19889
19890            println!("world");
19891        }
19892        "#
19893    .unindent();
19894    executor.run_until_parked();
19895    cx.set_state(
19896        &r#"
19897        use some::mod1;
19898        use some::mod2;
19899
19900        const A: u32 = 42;
19901        const B: u32 = 42;
19902        const C: u32 = 42;
19903        ˇ
19904
19905        fn main() {
19906            println!("hello");
19907
19908            println!("world");
19909        }
19910        "#
19911        .unindent(),
19912    );
19913
19914    cx.set_head_text(&diff_base);
19915    executor.run_until_parked();
19916
19917    cx.update_editor(|editor, window, cx| {
19918        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19919    });
19920    executor.run_until_parked();
19921
19922    cx.assert_state_with_diff(
19923        r#"
19924        use some::mod1;
19925        use some::mod2;
19926
19927        const A: u32 = 42;
19928      + const B: u32 = 42;
19929      + const C: u32 = 42;
19930      + ˇ
19931
19932        fn main() {
19933            println!("hello");
19934
19935            println!("world");
19936        }
19937      "#
19938        .unindent(),
19939    );
19940
19941    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
19942    executor.run_until_parked();
19943
19944    cx.assert_state_with_diff(
19945        r#"
19946        use some::mod1;
19947        use some::mod2;
19948
19949        const A: u32 = 42;
19950      + const B: u32 = 42;
19951      + const C: u32 = 42;
19952      + const D: u32 = 42;
19953      + ˇ
19954
19955        fn main() {
19956            println!("hello");
19957
19958            println!("world");
19959        }
19960      "#
19961        .unindent(),
19962    );
19963
19964    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
19965    executor.run_until_parked();
19966
19967    cx.assert_state_with_diff(
19968        r#"
19969        use some::mod1;
19970        use some::mod2;
19971
19972        const A: u32 = 42;
19973      + const B: u32 = 42;
19974      + const C: u32 = 42;
19975      + const D: u32 = 42;
19976      + const E: u32 = 42;
19977      + ˇ
19978
19979        fn main() {
19980            println!("hello");
19981
19982            println!("world");
19983        }
19984      "#
19985        .unindent(),
19986    );
19987
19988    cx.update_editor(|editor, window, cx| {
19989        editor.delete_line(&DeleteLine, window, cx);
19990    });
19991    executor.run_until_parked();
19992
19993    cx.assert_state_with_diff(
19994        r#"
19995        use some::mod1;
19996        use some::mod2;
19997
19998        const A: u32 = 42;
19999      + const B: u32 = 42;
20000      + const C: u32 = 42;
20001      + const D: u32 = 42;
20002      + const E: u32 = 42;
20003        ˇ
20004        fn main() {
20005            println!("hello");
20006
20007            println!("world");
20008        }
20009      "#
20010        .unindent(),
20011    );
20012
20013    cx.update_editor(|editor, window, cx| {
20014        editor.move_up(&MoveUp, window, cx);
20015        editor.delete_line(&DeleteLine, window, cx);
20016        editor.move_up(&MoveUp, window, cx);
20017        editor.delete_line(&DeleteLine, window, cx);
20018        editor.move_up(&MoveUp, window, cx);
20019        editor.delete_line(&DeleteLine, window, cx);
20020    });
20021    executor.run_until_parked();
20022    cx.assert_state_with_diff(
20023        r#"
20024        use some::mod1;
20025        use some::mod2;
20026
20027        const A: u32 = 42;
20028      + const B: u32 = 42;
20029        ˇ
20030        fn main() {
20031            println!("hello");
20032
20033            println!("world");
20034        }
20035      "#
20036        .unindent(),
20037    );
20038
20039    cx.update_editor(|editor, window, cx| {
20040        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
20041        editor.delete_line(&DeleteLine, window, cx);
20042    });
20043    executor.run_until_parked();
20044    cx.assert_state_with_diff(
20045        r#"
20046        ˇ
20047        fn main() {
20048            println!("hello");
20049
20050            println!("world");
20051        }
20052      "#
20053        .unindent(),
20054    );
20055}
20056
20057#[gpui::test]
20058async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
20059    init_test(cx, |_| {});
20060
20061    let mut cx = EditorTestContext::new(cx).await;
20062    cx.set_head_text(indoc! { "
20063        one
20064        two
20065        three
20066        four
20067        five
20068        "
20069    });
20070    cx.set_state(indoc! { "
20071        one
20072        ˇthree
20073        five
20074    "});
20075    cx.run_until_parked();
20076    cx.update_editor(|editor, window, cx| {
20077        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20078    });
20079    cx.assert_state_with_diff(
20080        indoc! { "
20081        one
20082      - two
20083        ˇthree
20084      - four
20085        five
20086    "}
20087        .to_string(),
20088    );
20089    cx.update_editor(|editor, window, cx| {
20090        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20091    });
20092
20093    cx.assert_state_with_diff(
20094        indoc! { "
20095        one
20096        ˇthree
20097        five
20098    "}
20099        .to_string(),
20100    );
20101
20102    cx.set_state(indoc! { "
20103        one
20104        ˇTWO
20105        three
20106        four
20107        five
20108    "});
20109    cx.run_until_parked();
20110    cx.update_editor(|editor, window, cx| {
20111        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20112    });
20113
20114    cx.assert_state_with_diff(
20115        indoc! { "
20116            one
20117          - two
20118          + ˇTWO
20119            three
20120            four
20121            five
20122        "}
20123        .to_string(),
20124    );
20125    cx.update_editor(|editor, window, cx| {
20126        editor.move_up(&Default::default(), window, cx);
20127        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20128    });
20129    cx.assert_state_with_diff(
20130        indoc! { "
20131            one
20132            ˇTWO
20133            three
20134            four
20135            five
20136        "}
20137        .to_string(),
20138    );
20139}
20140
20141#[gpui::test]
20142async fn test_edits_around_expanded_deletion_hunks(
20143    executor: BackgroundExecutor,
20144    cx: &mut TestAppContext,
20145) {
20146    init_test(cx, |_| {});
20147
20148    let mut cx = EditorTestContext::new(cx).await;
20149
20150    let diff_base = r#"
20151        use some::mod1;
20152        use some::mod2;
20153
20154        const A: u32 = 42;
20155        const B: u32 = 42;
20156        const C: u32 = 42;
20157
20158
20159        fn main() {
20160            println!("hello");
20161
20162            println!("world");
20163        }
20164    "#
20165    .unindent();
20166    executor.run_until_parked();
20167    cx.set_state(
20168        &r#"
20169        use some::mod1;
20170        use some::mod2;
20171
20172        ˇconst B: u32 = 42;
20173        const C: u32 = 42;
20174
20175
20176        fn main() {
20177            println!("hello");
20178
20179            println!("world");
20180        }
20181        "#
20182        .unindent(),
20183    );
20184
20185    cx.set_head_text(&diff_base);
20186    executor.run_until_parked();
20187
20188    cx.update_editor(|editor, window, cx| {
20189        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20190    });
20191    executor.run_until_parked();
20192
20193    cx.assert_state_with_diff(
20194        r#"
20195        use some::mod1;
20196        use some::mod2;
20197
20198      - const A: u32 = 42;
20199        ˇconst B: u32 = 42;
20200        const C: u32 = 42;
20201
20202
20203        fn main() {
20204            println!("hello");
20205
20206            println!("world");
20207        }
20208      "#
20209        .unindent(),
20210    );
20211
20212    cx.update_editor(|editor, window, cx| {
20213        editor.delete_line(&DeleteLine, window, cx);
20214    });
20215    executor.run_until_parked();
20216    cx.assert_state_with_diff(
20217        r#"
20218        use some::mod1;
20219        use some::mod2;
20220
20221      - const A: u32 = 42;
20222      - const B: u32 = 42;
20223        ˇconst C: u32 = 42;
20224
20225
20226        fn main() {
20227            println!("hello");
20228
20229            println!("world");
20230        }
20231      "#
20232        .unindent(),
20233    );
20234
20235    cx.update_editor(|editor, window, cx| {
20236        editor.delete_line(&DeleteLine, window, cx);
20237    });
20238    executor.run_until_parked();
20239    cx.assert_state_with_diff(
20240        r#"
20241        use some::mod1;
20242        use some::mod2;
20243
20244      - const A: u32 = 42;
20245      - const B: u32 = 42;
20246      - const C: u32 = 42;
20247        ˇ
20248
20249        fn main() {
20250            println!("hello");
20251
20252            println!("world");
20253        }
20254      "#
20255        .unindent(),
20256    );
20257
20258    cx.update_editor(|editor, window, cx| {
20259        editor.handle_input("replacement", window, cx);
20260    });
20261    executor.run_until_parked();
20262    cx.assert_state_with_diff(
20263        r#"
20264        use some::mod1;
20265        use some::mod2;
20266
20267      - const A: u32 = 42;
20268      - const B: u32 = 42;
20269      - const C: u32 = 42;
20270      -
20271      + replacementˇ
20272
20273        fn main() {
20274            println!("hello");
20275
20276            println!("world");
20277        }
20278      "#
20279        .unindent(),
20280    );
20281}
20282
20283#[gpui::test]
20284async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20285    init_test(cx, |_| {});
20286
20287    let mut cx = EditorTestContext::new(cx).await;
20288
20289    let base_text = r#"
20290        one
20291        two
20292        three
20293        four
20294        five
20295    "#
20296    .unindent();
20297    executor.run_until_parked();
20298    cx.set_state(
20299        &r#"
20300        one
20301        two
20302        fˇour
20303        five
20304        "#
20305        .unindent(),
20306    );
20307
20308    cx.set_head_text(&base_text);
20309    executor.run_until_parked();
20310
20311    cx.update_editor(|editor, window, cx| {
20312        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20313    });
20314    executor.run_until_parked();
20315
20316    cx.assert_state_with_diff(
20317        r#"
20318          one
20319          two
20320        - three
20321          fˇour
20322          five
20323        "#
20324        .unindent(),
20325    );
20326
20327    cx.update_editor(|editor, window, cx| {
20328        editor.backspace(&Backspace, window, cx);
20329        editor.backspace(&Backspace, window, cx);
20330    });
20331    executor.run_until_parked();
20332    cx.assert_state_with_diff(
20333        r#"
20334          one
20335          two
20336        - threeˇ
20337        - four
20338        + our
20339          five
20340        "#
20341        .unindent(),
20342    );
20343}
20344
20345#[gpui::test]
20346async fn test_edit_after_expanded_modification_hunk(
20347    executor: BackgroundExecutor,
20348    cx: &mut TestAppContext,
20349) {
20350    init_test(cx, |_| {});
20351
20352    let mut cx = EditorTestContext::new(cx).await;
20353
20354    let diff_base = r#"
20355        use some::mod1;
20356        use some::mod2;
20357
20358        const A: u32 = 42;
20359        const B: u32 = 42;
20360        const C: u32 = 42;
20361        const D: u32 = 42;
20362
20363
20364        fn main() {
20365            println!("hello");
20366
20367            println!("world");
20368        }"#
20369    .unindent();
20370
20371    cx.set_state(
20372        &r#"
20373        use some::mod1;
20374        use some::mod2;
20375
20376        const A: u32 = 42;
20377        const B: u32 = 42;
20378        const C: u32 = 43ˇ
20379        const D: u32 = 42;
20380
20381
20382        fn main() {
20383            println!("hello");
20384
20385            println!("world");
20386        }"#
20387        .unindent(),
20388    );
20389
20390    cx.set_head_text(&diff_base);
20391    executor.run_until_parked();
20392    cx.update_editor(|editor, window, cx| {
20393        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20394    });
20395    executor.run_until_parked();
20396
20397    cx.assert_state_with_diff(
20398        r#"
20399        use some::mod1;
20400        use some::mod2;
20401
20402        const A: u32 = 42;
20403        const B: u32 = 42;
20404      - const C: u32 = 42;
20405      + const C: u32 = 43ˇ
20406        const D: u32 = 42;
20407
20408
20409        fn main() {
20410            println!("hello");
20411
20412            println!("world");
20413        }"#
20414        .unindent(),
20415    );
20416
20417    cx.update_editor(|editor, window, cx| {
20418        editor.handle_input("\nnew_line\n", window, cx);
20419    });
20420    executor.run_until_parked();
20421
20422    cx.assert_state_with_diff(
20423        r#"
20424        use some::mod1;
20425        use some::mod2;
20426
20427        const A: u32 = 42;
20428        const B: u32 = 42;
20429      - const C: u32 = 42;
20430      + const C: u32 = 43
20431      + new_line
20432      + ˇ
20433        const D: u32 = 42;
20434
20435
20436        fn main() {
20437            println!("hello");
20438
20439            println!("world");
20440        }"#
20441        .unindent(),
20442    );
20443}
20444
20445#[gpui::test]
20446async fn test_stage_and_unstage_added_file_hunk(
20447    executor: BackgroundExecutor,
20448    cx: &mut TestAppContext,
20449) {
20450    init_test(cx, |_| {});
20451
20452    let mut cx = EditorTestContext::new(cx).await;
20453    cx.update_editor(|editor, _, cx| {
20454        editor.set_expand_all_diff_hunks(cx);
20455    });
20456
20457    let working_copy = r#"
20458            ˇfn main() {
20459                println!("hello, world!");
20460            }
20461        "#
20462    .unindent();
20463
20464    cx.set_state(&working_copy);
20465    executor.run_until_parked();
20466
20467    cx.assert_state_with_diff(
20468        r#"
20469            + ˇfn main() {
20470            +     println!("hello, world!");
20471            + }
20472        "#
20473        .unindent(),
20474    );
20475    cx.assert_index_text(None);
20476
20477    cx.update_editor(|editor, window, cx| {
20478        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20479    });
20480    executor.run_until_parked();
20481    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
20482    cx.assert_state_with_diff(
20483        r#"
20484            + ˇfn main() {
20485            +     println!("hello, world!");
20486            + }
20487        "#
20488        .unindent(),
20489    );
20490
20491    cx.update_editor(|editor, window, cx| {
20492        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20493    });
20494    executor.run_until_parked();
20495    cx.assert_index_text(None);
20496}
20497
20498async fn setup_indent_guides_editor(
20499    text: &str,
20500    cx: &mut TestAppContext,
20501) -> (BufferId, EditorTestContext) {
20502    init_test(cx, |_| {});
20503
20504    let mut cx = EditorTestContext::new(cx).await;
20505
20506    let buffer_id = cx.update_editor(|editor, window, cx| {
20507        editor.set_text(text, window, cx);
20508        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
20509
20510        buffer_ids[0]
20511    });
20512
20513    (buffer_id, cx)
20514}
20515
20516fn assert_indent_guides(
20517    range: Range<u32>,
20518    expected: Vec<IndentGuide>,
20519    active_indices: Option<Vec<usize>>,
20520    cx: &mut EditorTestContext,
20521) {
20522    let indent_guides = cx.update_editor(|editor, window, cx| {
20523        let snapshot = editor.snapshot(window, cx).display_snapshot;
20524        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
20525            editor,
20526            MultiBufferRow(range.start)..MultiBufferRow(range.end),
20527            true,
20528            &snapshot,
20529            cx,
20530        );
20531
20532        indent_guides.sort_by(|a, b| {
20533            a.depth.cmp(&b.depth).then(
20534                a.start_row
20535                    .cmp(&b.start_row)
20536                    .then(a.end_row.cmp(&b.end_row)),
20537            )
20538        });
20539        indent_guides
20540    });
20541
20542    if let Some(expected) = active_indices {
20543        let active_indices = cx.update_editor(|editor, window, cx| {
20544            let snapshot = editor.snapshot(window, cx).display_snapshot;
20545            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
20546        });
20547
20548        assert_eq!(
20549            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
20550            expected,
20551            "Active indent guide indices do not match"
20552        );
20553    }
20554
20555    assert_eq!(indent_guides, expected, "Indent guides do not match");
20556}
20557
20558fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
20559    IndentGuide {
20560        buffer_id,
20561        start_row: MultiBufferRow(start_row),
20562        end_row: MultiBufferRow(end_row),
20563        depth,
20564        tab_size: 4,
20565        settings: IndentGuideSettings {
20566            enabled: true,
20567            line_width: 1,
20568            active_line_width: 1,
20569            coloring: IndentGuideColoring::default(),
20570            background_coloring: IndentGuideBackgroundColoring::default(),
20571        },
20572    }
20573}
20574
20575#[gpui::test]
20576async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
20577    let (buffer_id, mut cx) = setup_indent_guides_editor(
20578        &"
20579        fn main() {
20580            let a = 1;
20581        }"
20582        .unindent(),
20583        cx,
20584    )
20585    .await;
20586
20587    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20588}
20589
20590#[gpui::test]
20591async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
20592    let (buffer_id, mut cx) = setup_indent_guides_editor(
20593        &"
20594        fn main() {
20595            let a = 1;
20596            let b = 2;
20597        }"
20598        .unindent(),
20599        cx,
20600    )
20601    .await;
20602
20603    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
20604}
20605
20606#[gpui::test]
20607async fn test_indent_guide_nested(cx: &mut TestAppContext) {
20608    let (buffer_id, mut cx) = setup_indent_guides_editor(
20609        &"
20610        fn main() {
20611            let a = 1;
20612            if a == 3 {
20613                let b = 2;
20614            } else {
20615                let c = 3;
20616            }
20617        }"
20618        .unindent(),
20619        cx,
20620    )
20621    .await;
20622
20623    assert_indent_guides(
20624        0..8,
20625        vec![
20626            indent_guide(buffer_id, 1, 6, 0),
20627            indent_guide(buffer_id, 3, 3, 1),
20628            indent_guide(buffer_id, 5, 5, 1),
20629        ],
20630        None,
20631        &mut cx,
20632    );
20633}
20634
20635#[gpui::test]
20636async fn test_indent_guide_tab(cx: &mut TestAppContext) {
20637    let (buffer_id, mut cx) = setup_indent_guides_editor(
20638        &"
20639        fn main() {
20640            let a = 1;
20641                let b = 2;
20642            let c = 3;
20643        }"
20644        .unindent(),
20645        cx,
20646    )
20647    .await;
20648
20649    assert_indent_guides(
20650        0..5,
20651        vec![
20652            indent_guide(buffer_id, 1, 3, 0),
20653            indent_guide(buffer_id, 2, 2, 1),
20654        ],
20655        None,
20656        &mut cx,
20657    );
20658}
20659
20660#[gpui::test]
20661async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
20662    let (buffer_id, mut cx) = setup_indent_guides_editor(
20663        &"
20664        fn main() {
20665            let a = 1;
20666
20667            let c = 3;
20668        }"
20669        .unindent(),
20670        cx,
20671    )
20672    .await;
20673
20674    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
20675}
20676
20677#[gpui::test]
20678async fn test_indent_guide_complex(cx: &mut TestAppContext) {
20679    let (buffer_id, mut cx) = setup_indent_guides_editor(
20680        &"
20681        fn main() {
20682            let a = 1;
20683
20684            let c = 3;
20685
20686            if a == 3 {
20687                let b = 2;
20688            } else {
20689                let c = 3;
20690            }
20691        }"
20692        .unindent(),
20693        cx,
20694    )
20695    .await;
20696
20697    assert_indent_guides(
20698        0..11,
20699        vec![
20700            indent_guide(buffer_id, 1, 9, 0),
20701            indent_guide(buffer_id, 6, 6, 1),
20702            indent_guide(buffer_id, 8, 8, 1),
20703        ],
20704        None,
20705        &mut cx,
20706    );
20707}
20708
20709#[gpui::test]
20710async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
20711    let (buffer_id, mut cx) = setup_indent_guides_editor(
20712        &"
20713        fn main() {
20714            let a = 1;
20715
20716            let c = 3;
20717
20718            if a == 3 {
20719                let b = 2;
20720            } else {
20721                let c = 3;
20722            }
20723        }"
20724        .unindent(),
20725        cx,
20726    )
20727    .await;
20728
20729    assert_indent_guides(
20730        1..11,
20731        vec![
20732            indent_guide(buffer_id, 1, 9, 0),
20733            indent_guide(buffer_id, 6, 6, 1),
20734            indent_guide(buffer_id, 8, 8, 1),
20735        ],
20736        None,
20737        &mut cx,
20738    );
20739}
20740
20741#[gpui::test]
20742async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
20743    let (buffer_id, mut cx) = setup_indent_guides_editor(
20744        &"
20745        fn main() {
20746            let a = 1;
20747
20748            let c = 3;
20749
20750            if a == 3 {
20751                let b = 2;
20752            } else {
20753                let c = 3;
20754            }
20755        }"
20756        .unindent(),
20757        cx,
20758    )
20759    .await;
20760
20761    assert_indent_guides(
20762        1..10,
20763        vec![
20764            indent_guide(buffer_id, 1, 9, 0),
20765            indent_guide(buffer_id, 6, 6, 1),
20766            indent_guide(buffer_id, 8, 8, 1),
20767        ],
20768        None,
20769        &mut cx,
20770    );
20771}
20772
20773#[gpui::test]
20774async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
20775    let (buffer_id, mut cx) = setup_indent_guides_editor(
20776        &"
20777        fn main() {
20778            if a {
20779                b(
20780                    c,
20781                    d,
20782                )
20783            } else {
20784                e(
20785                    f
20786                )
20787            }
20788        }"
20789        .unindent(),
20790        cx,
20791    )
20792    .await;
20793
20794    assert_indent_guides(
20795        0..11,
20796        vec![
20797            indent_guide(buffer_id, 1, 10, 0),
20798            indent_guide(buffer_id, 2, 5, 1),
20799            indent_guide(buffer_id, 7, 9, 1),
20800            indent_guide(buffer_id, 3, 4, 2),
20801            indent_guide(buffer_id, 8, 8, 2),
20802        ],
20803        None,
20804        &mut cx,
20805    );
20806
20807    cx.update_editor(|editor, window, cx| {
20808        editor.fold_at(MultiBufferRow(2), window, cx);
20809        assert_eq!(
20810            editor.display_text(cx),
20811            "
20812            fn main() {
20813                if a {
20814                    b(⋯
20815                    )
20816                } else {
20817                    e(
20818                        f
20819                    )
20820                }
20821            }"
20822            .unindent()
20823        );
20824    });
20825
20826    assert_indent_guides(
20827        0..11,
20828        vec![
20829            indent_guide(buffer_id, 1, 10, 0),
20830            indent_guide(buffer_id, 2, 5, 1),
20831            indent_guide(buffer_id, 7, 9, 1),
20832            indent_guide(buffer_id, 8, 8, 2),
20833        ],
20834        None,
20835        &mut cx,
20836    );
20837}
20838
20839#[gpui::test]
20840async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
20841    let (buffer_id, mut cx) = setup_indent_guides_editor(
20842        &"
20843        block1
20844            block2
20845                block3
20846                    block4
20847            block2
20848        block1
20849        block1"
20850            .unindent(),
20851        cx,
20852    )
20853    .await;
20854
20855    assert_indent_guides(
20856        1..10,
20857        vec![
20858            indent_guide(buffer_id, 1, 4, 0),
20859            indent_guide(buffer_id, 2, 3, 1),
20860            indent_guide(buffer_id, 3, 3, 2),
20861        ],
20862        None,
20863        &mut cx,
20864    );
20865}
20866
20867#[gpui::test]
20868async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
20869    let (buffer_id, mut cx) = setup_indent_guides_editor(
20870        &"
20871        block1
20872            block2
20873                block3
20874
20875        block1
20876        block1"
20877            .unindent(),
20878        cx,
20879    )
20880    .await;
20881
20882    assert_indent_guides(
20883        0..6,
20884        vec![
20885            indent_guide(buffer_id, 1, 2, 0),
20886            indent_guide(buffer_id, 2, 2, 1),
20887        ],
20888        None,
20889        &mut cx,
20890    );
20891}
20892
20893#[gpui::test]
20894async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
20895    let (buffer_id, mut cx) = setup_indent_guides_editor(
20896        &"
20897        function component() {
20898        \treturn (
20899        \t\t\t
20900        \t\t<div>
20901        \t\t\t<abc></abc>
20902        \t\t</div>
20903        \t)
20904        }"
20905        .unindent(),
20906        cx,
20907    )
20908    .await;
20909
20910    assert_indent_guides(
20911        0..8,
20912        vec![
20913            indent_guide(buffer_id, 1, 6, 0),
20914            indent_guide(buffer_id, 2, 5, 1),
20915            indent_guide(buffer_id, 4, 4, 2),
20916        ],
20917        None,
20918        &mut cx,
20919    );
20920}
20921
20922#[gpui::test]
20923async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
20924    let (buffer_id, mut cx) = setup_indent_guides_editor(
20925        &"
20926        function component() {
20927        \treturn (
20928        \t
20929        \t\t<div>
20930        \t\t\t<abc></abc>
20931        \t\t</div>
20932        \t)
20933        }"
20934        .unindent(),
20935        cx,
20936    )
20937    .await;
20938
20939    assert_indent_guides(
20940        0..8,
20941        vec![
20942            indent_guide(buffer_id, 1, 6, 0),
20943            indent_guide(buffer_id, 2, 5, 1),
20944            indent_guide(buffer_id, 4, 4, 2),
20945        ],
20946        None,
20947        &mut cx,
20948    );
20949}
20950
20951#[gpui::test]
20952async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
20953    let (buffer_id, mut cx) = setup_indent_guides_editor(
20954        &"
20955        block1
20956
20957
20958
20959            block2
20960        "
20961        .unindent(),
20962        cx,
20963    )
20964    .await;
20965
20966    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20967}
20968
20969#[gpui::test]
20970async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
20971    let (buffer_id, mut cx) = setup_indent_guides_editor(
20972        &"
20973        def a:
20974        \tb = 3
20975        \tif True:
20976        \t\tc = 4
20977        \t\td = 5
20978        \tprint(b)
20979        "
20980        .unindent(),
20981        cx,
20982    )
20983    .await;
20984
20985    assert_indent_guides(
20986        0..6,
20987        vec![
20988            indent_guide(buffer_id, 1, 5, 0),
20989            indent_guide(buffer_id, 3, 4, 1),
20990        ],
20991        None,
20992        &mut cx,
20993    );
20994}
20995
20996#[gpui::test]
20997async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
20998    let (buffer_id, mut cx) = setup_indent_guides_editor(
20999        &"
21000    fn main() {
21001        let a = 1;
21002    }"
21003        .unindent(),
21004        cx,
21005    )
21006    .await;
21007
21008    cx.update_editor(|editor, window, cx| {
21009        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21010            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
21011        });
21012    });
21013
21014    assert_indent_guides(
21015        0..3,
21016        vec![indent_guide(buffer_id, 1, 1, 0)],
21017        Some(vec![0]),
21018        &mut cx,
21019    );
21020}
21021
21022#[gpui::test]
21023async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
21024    let (buffer_id, mut cx) = setup_indent_guides_editor(
21025        &"
21026    fn main() {
21027        if 1 == 2 {
21028            let a = 1;
21029        }
21030    }"
21031        .unindent(),
21032        cx,
21033    )
21034    .await;
21035
21036    cx.update_editor(|editor, window, cx| {
21037        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21038            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
21039        });
21040    });
21041
21042    assert_indent_guides(
21043        0..4,
21044        vec![
21045            indent_guide(buffer_id, 1, 3, 0),
21046            indent_guide(buffer_id, 2, 2, 1),
21047        ],
21048        Some(vec![1]),
21049        &mut cx,
21050    );
21051
21052    cx.update_editor(|editor, window, cx| {
21053        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21054            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
21055        });
21056    });
21057
21058    assert_indent_guides(
21059        0..4,
21060        vec![
21061            indent_guide(buffer_id, 1, 3, 0),
21062            indent_guide(buffer_id, 2, 2, 1),
21063        ],
21064        Some(vec![1]),
21065        &mut cx,
21066    );
21067
21068    cx.update_editor(|editor, window, cx| {
21069        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21070            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
21071        });
21072    });
21073
21074    assert_indent_guides(
21075        0..4,
21076        vec![
21077            indent_guide(buffer_id, 1, 3, 0),
21078            indent_guide(buffer_id, 2, 2, 1),
21079        ],
21080        Some(vec![0]),
21081        &mut cx,
21082    );
21083}
21084
21085#[gpui::test]
21086async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
21087    let (buffer_id, mut cx) = setup_indent_guides_editor(
21088        &"
21089    fn main() {
21090        let a = 1;
21091
21092        let b = 2;
21093    }"
21094        .unindent(),
21095        cx,
21096    )
21097    .await;
21098
21099    cx.update_editor(|editor, window, cx| {
21100        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21101            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
21102        });
21103    });
21104
21105    assert_indent_guides(
21106        0..5,
21107        vec![indent_guide(buffer_id, 1, 3, 0)],
21108        Some(vec![0]),
21109        &mut cx,
21110    );
21111}
21112
21113#[gpui::test]
21114async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
21115    let (buffer_id, mut cx) = setup_indent_guides_editor(
21116        &"
21117    def m:
21118        a = 1
21119        pass"
21120            .unindent(),
21121        cx,
21122    )
21123    .await;
21124
21125    cx.update_editor(|editor, window, cx| {
21126        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21127            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
21128        });
21129    });
21130
21131    assert_indent_guides(
21132        0..3,
21133        vec![indent_guide(buffer_id, 1, 2, 0)],
21134        Some(vec![0]),
21135        &mut cx,
21136    );
21137}
21138
21139#[gpui::test]
21140async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
21141    init_test(cx, |_| {});
21142    let mut cx = EditorTestContext::new(cx).await;
21143    let text = indoc! {
21144        "
21145        impl A {
21146            fn b() {
21147                0;
21148                3;
21149                5;
21150                6;
21151                7;
21152            }
21153        }
21154        "
21155    };
21156    let base_text = indoc! {
21157        "
21158        impl A {
21159            fn b() {
21160                0;
21161                1;
21162                2;
21163                3;
21164                4;
21165            }
21166            fn c() {
21167                5;
21168                6;
21169                7;
21170            }
21171        }
21172        "
21173    };
21174
21175    cx.update_editor(|editor, window, cx| {
21176        editor.set_text(text, window, cx);
21177
21178        editor.buffer().update(cx, |multibuffer, cx| {
21179            let buffer = multibuffer.as_singleton().unwrap();
21180            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
21181
21182            multibuffer.set_all_diff_hunks_expanded(cx);
21183            multibuffer.add_diff(diff, cx);
21184
21185            buffer.read(cx).remote_id()
21186        })
21187    });
21188    cx.run_until_parked();
21189
21190    cx.assert_state_with_diff(
21191        indoc! { "
21192          impl A {
21193              fn b() {
21194                  0;
21195        -         1;
21196        -         2;
21197                  3;
21198        -         4;
21199        -     }
21200        -     fn c() {
21201                  5;
21202                  6;
21203                  7;
21204              }
21205          }
21206          ˇ"
21207        }
21208        .to_string(),
21209    );
21210
21211    let mut actual_guides = cx.update_editor(|editor, window, cx| {
21212        editor
21213            .snapshot(window, cx)
21214            .buffer_snapshot()
21215            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
21216            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
21217            .collect::<Vec<_>>()
21218    });
21219    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
21220    assert_eq!(
21221        actual_guides,
21222        vec![
21223            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
21224            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
21225            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
21226        ]
21227    );
21228}
21229
21230#[gpui::test]
21231async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
21232    init_test(cx, |_| {});
21233    let mut cx = EditorTestContext::new(cx).await;
21234
21235    let diff_base = r#"
21236        a
21237        b
21238        c
21239        "#
21240    .unindent();
21241
21242    cx.set_state(
21243        &r#"
21244        ˇA
21245        b
21246        C
21247        "#
21248        .unindent(),
21249    );
21250    cx.set_head_text(&diff_base);
21251    cx.update_editor(|editor, window, cx| {
21252        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21253    });
21254    executor.run_until_parked();
21255
21256    let both_hunks_expanded = r#"
21257        - a
21258        + ˇA
21259          b
21260        - c
21261        + C
21262        "#
21263    .unindent();
21264
21265    cx.assert_state_with_diff(both_hunks_expanded.clone());
21266
21267    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21268        let snapshot = editor.snapshot(window, cx);
21269        let hunks = editor
21270            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21271            .collect::<Vec<_>>();
21272        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21273        let buffer_id = hunks[0].buffer_id;
21274        hunks
21275            .into_iter()
21276            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
21277            .collect::<Vec<_>>()
21278    });
21279    assert_eq!(hunk_ranges.len(), 2);
21280
21281    cx.update_editor(|editor, _, cx| {
21282        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21283    });
21284    executor.run_until_parked();
21285
21286    let second_hunk_expanded = r#"
21287          ˇA
21288          b
21289        - c
21290        + C
21291        "#
21292    .unindent();
21293
21294    cx.assert_state_with_diff(second_hunk_expanded);
21295
21296    cx.update_editor(|editor, _, cx| {
21297        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21298    });
21299    executor.run_until_parked();
21300
21301    cx.assert_state_with_diff(both_hunks_expanded.clone());
21302
21303    cx.update_editor(|editor, _, cx| {
21304        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21305    });
21306    executor.run_until_parked();
21307
21308    let first_hunk_expanded = r#"
21309        - a
21310        + ˇA
21311          b
21312          C
21313        "#
21314    .unindent();
21315
21316    cx.assert_state_with_diff(first_hunk_expanded);
21317
21318    cx.update_editor(|editor, _, cx| {
21319        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21320    });
21321    executor.run_until_parked();
21322
21323    cx.assert_state_with_diff(both_hunks_expanded);
21324
21325    cx.set_state(
21326        &r#"
21327        ˇA
21328        b
21329        "#
21330        .unindent(),
21331    );
21332    cx.run_until_parked();
21333
21334    // TODO this cursor position seems bad
21335    cx.assert_state_with_diff(
21336        r#"
21337        - ˇa
21338        + A
21339          b
21340        "#
21341        .unindent(),
21342    );
21343
21344    cx.update_editor(|editor, window, cx| {
21345        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21346    });
21347
21348    cx.assert_state_with_diff(
21349        r#"
21350            - ˇa
21351            + A
21352              b
21353            - c
21354            "#
21355        .unindent(),
21356    );
21357
21358    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21359        let snapshot = editor.snapshot(window, cx);
21360        let hunks = editor
21361            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21362            .collect::<Vec<_>>();
21363        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21364        let buffer_id = hunks[0].buffer_id;
21365        hunks
21366            .into_iter()
21367            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
21368            .collect::<Vec<_>>()
21369    });
21370    assert_eq!(hunk_ranges.len(), 2);
21371
21372    cx.update_editor(|editor, _, cx| {
21373        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21374    });
21375    executor.run_until_parked();
21376
21377    cx.assert_state_with_diff(
21378        r#"
21379        - ˇa
21380        + A
21381          b
21382        "#
21383        .unindent(),
21384    );
21385}
21386
21387#[gpui::test]
21388async fn test_toggle_deletion_hunk_at_start_of_file(
21389    executor: BackgroundExecutor,
21390    cx: &mut TestAppContext,
21391) {
21392    init_test(cx, |_| {});
21393    let mut cx = EditorTestContext::new(cx).await;
21394
21395    let diff_base = r#"
21396        a
21397        b
21398        c
21399        "#
21400    .unindent();
21401
21402    cx.set_state(
21403        &r#"
21404        ˇb
21405        c
21406        "#
21407        .unindent(),
21408    );
21409    cx.set_head_text(&diff_base);
21410    cx.update_editor(|editor, window, cx| {
21411        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21412    });
21413    executor.run_until_parked();
21414
21415    let hunk_expanded = r#"
21416        - a
21417          ˇb
21418          c
21419        "#
21420    .unindent();
21421
21422    cx.assert_state_with_diff(hunk_expanded.clone());
21423
21424    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21425        let snapshot = editor.snapshot(window, cx);
21426        let hunks = editor
21427            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21428            .collect::<Vec<_>>();
21429        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21430        let buffer_id = hunks[0].buffer_id;
21431        hunks
21432            .into_iter()
21433            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
21434            .collect::<Vec<_>>()
21435    });
21436    assert_eq!(hunk_ranges.len(), 1);
21437
21438    cx.update_editor(|editor, _, cx| {
21439        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21440    });
21441    executor.run_until_parked();
21442
21443    let hunk_collapsed = r#"
21444          ˇb
21445          c
21446        "#
21447    .unindent();
21448
21449    cx.assert_state_with_diff(hunk_collapsed);
21450
21451    cx.update_editor(|editor, _, cx| {
21452        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21453    });
21454    executor.run_until_parked();
21455
21456    cx.assert_state_with_diff(hunk_expanded);
21457}
21458
21459#[gpui::test]
21460async fn test_display_diff_hunks(cx: &mut TestAppContext) {
21461    init_test(cx, |_| {});
21462
21463    let fs = FakeFs::new(cx.executor());
21464    fs.insert_tree(
21465        path!("/test"),
21466        json!({
21467            ".git": {},
21468            "file-1": "ONE\n",
21469            "file-2": "TWO\n",
21470            "file-3": "THREE\n",
21471        }),
21472    )
21473    .await;
21474
21475    fs.set_head_for_repo(
21476        path!("/test/.git").as_ref(),
21477        &[
21478            ("file-1", "one\n".into()),
21479            ("file-2", "two\n".into()),
21480            ("file-3", "three\n".into()),
21481        ],
21482        "deadbeef",
21483    );
21484
21485    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
21486    let mut buffers = vec![];
21487    for i in 1..=3 {
21488        let buffer = project
21489            .update(cx, |project, cx| {
21490                let path = format!(path!("/test/file-{}"), i);
21491                project.open_local_buffer(path, cx)
21492            })
21493            .await
21494            .unwrap();
21495        buffers.push(buffer);
21496    }
21497
21498    let multibuffer = cx.new(|cx| {
21499        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
21500        multibuffer.set_all_diff_hunks_expanded(cx);
21501        for buffer in &buffers {
21502            let snapshot = buffer.read(cx).snapshot();
21503            multibuffer.set_excerpts_for_path(
21504                PathKey::with_sort_prefix(0, buffer.read(cx).file().unwrap().path().clone()),
21505                buffer.clone(),
21506                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
21507                2,
21508                cx,
21509            );
21510        }
21511        multibuffer
21512    });
21513
21514    let editor = cx.add_window(|window, cx| {
21515        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
21516    });
21517    cx.run_until_parked();
21518
21519    let snapshot = editor
21520        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21521        .unwrap();
21522    let hunks = snapshot
21523        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
21524        .map(|hunk| match hunk {
21525            DisplayDiffHunk::Unfolded {
21526                display_row_range, ..
21527            } => display_row_range,
21528            DisplayDiffHunk::Folded { .. } => unreachable!(),
21529        })
21530        .collect::<Vec<_>>();
21531    assert_eq!(
21532        hunks,
21533        [
21534            DisplayRow(2)..DisplayRow(4),
21535            DisplayRow(7)..DisplayRow(9),
21536            DisplayRow(12)..DisplayRow(14),
21537        ]
21538    );
21539}
21540
21541#[gpui::test]
21542async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
21543    init_test(cx, |_| {});
21544
21545    let mut cx = EditorTestContext::new(cx).await;
21546    cx.set_head_text(indoc! { "
21547        one
21548        two
21549        three
21550        four
21551        five
21552        "
21553    });
21554    cx.set_index_text(indoc! { "
21555        one
21556        two
21557        three
21558        four
21559        five
21560        "
21561    });
21562    cx.set_state(indoc! {"
21563        one
21564        TWO
21565        ˇTHREE
21566        FOUR
21567        five
21568    "});
21569    cx.run_until_parked();
21570    cx.update_editor(|editor, window, cx| {
21571        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21572    });
21573    cx.run_until_parked();
21574    cx.assert_index_text(Some(indoc! {"
21575        one
21576        TWO
21577        THREE
21578        FOUR
21579        five
21580    "}));
21581    cx.set_state(indoc! { "
21582        one
21583        TWO
21584        ˇTHREE-HUNDRED
21585        FOUR
21586        five
21587    "});
21588    cx.run_until_parked();
21589    cx.update_editor(|editor, window, cx| {
21590        let snapshot = editor.snapshot(window, cx);
21591        let hunks = editor
21592            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21593            .collect::<Vec<_>>();
21594        assert_eq!(hunks.len(), 1);
21595        assert_eq!(
21596            hunks[0].status(),
21597            DiffHunkStatus {
21598                kind: DiffHunkStatusKind::Modified,
21599                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
21600            }
21601        );
21602
21603        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21604    });
21605    cx.run_until_parked();
21606    cx.assert_index_text(Some(indoc! {"
21607        one
21608        TWO
21609        THREE-HUNDRED
21610        FOUR
21611        five
21612    "}));
21613}
21614
21615#[gpui::test]
21616fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
21617    init_test(cx, |_| {});
21618
21619    let editor = cx.add_window(|window, cx| {
21620        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
21621        build_editor(buffer, window, cx)
21622    });
21623
21624    let render_args = Arc::new(Mutex::new(None));
21625    let snapshot = editor
21626        .update(cx, |editor, window, cx| {
21627            let snapshot = editor.buffer().read(cx).snapshot(cx);
21628            let range =
21629                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
21630
21631            struct RenderArgs {
21632                row: MultiBufferRow,
21633                folded: bool,
21634                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
21635            }
21636
21637            let crease = Crease::inline(
21638                range,
21639                FoldPlaceholder::test(),
21640                {
21641                    let toggle_callback = render_args.clone();
21642                    move |row, folded, callback, _window, _cx| {
21643                        *toggle_callback.lock() = Some(RenderArgs {
21644                            row,
21645                            folded,
21646                            callback,
21647                        });
21648                        div()
21649                    }
21650                },
21651                |_row, _folded, _window, _cx| div(),
21652            );
21653
21654            editor.insert_creases(Some(crease), cx);
21655            let snapshot = editor.snapshot(window, cx);
21656            let _div =
21657                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
21658            snapshot
21659        })
21660        .unwrap();
21661
21662    let render_args = render_args.lock().take().unwrap();
21663    assert_eq!(render_args.row, MultiBufferRow(1));
21664    assert!(!render_args.folded);
21665    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21666
21667    cx.update_window(*editor, |_, window, cx| {
21668        (render_args.callback)(true, window, cx)
21669    })
21670    .unwrap();
21671    let snapshot = editor
21672        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21673        .unwrap();
21674    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
21675
21676    cx.update_window(*editor, |_, window, cx| {
21677        (render_args.callback)(false, window, cx)
21678    })
21679    .unwrap();
21680    let snapshot = editor
21681        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21682        .unwrap();
21683    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21684}
21685
21686#[gpui::test]
21687async fn test_input_text(cx: &mut TestAppContext) {
21688    init_test(cx, |_| {});
21689    let mut cx = EditorTestContext::new(cx).await;
21690
21691    cx.set_state(
21692        &r#"ˇone
21693        two
21694
21695        three
21696        fourˇ
21697        five
21698
21699        siˇx"#
21700            .unindent(),
21701    );
21702
21703    cx.dispatch_action(HandleInput(String::new()));
21704    cx.assert_editor_state(
21705        &r#"ˇone
21706        two
21707
21708        three
21709        fourˇ
21710        five
21711
21712        siˇx"#
21713            .unindent(),
21714    );
21715
21716    cx.dispatch_action(HandleInput("AAAA".to_string()));
21717    cx.assert_editor_state(
21718        &r#"AAAAˇone
21719        two
21720
21721        three
21722        fourAAAAˇ
21723        five
21724
21725        siAAAAˇx"#
21726            .unindent(),
21727    );
21728}
21729
21730#[gpui::test]
21731async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
21732    init_test(cx, |_| {});
21733
21734    let mut cx = EditorTestContext::new(cx).await;
21735    cx.set_state(
21736        r#"let foo = 1;
21737let foo = 2;
21738let foo = 3;
21739let fooˇ = 4;
21740let foo = 5;
21741let foo = 6;
21742let foo = 7;
21743let foo = 8;
21744let foo = 9;
21745let foo = 10;
21746let foo = 11;
21747let foo = 12;
21748let foo = 13;
21749let foo = 14;
21750let foo = 15;"#,
21751    );
21752
21753    cx.update_editor(|e, window, cx| {
21754        assert_eq!(
21755            e.next_scroll_position,
21756            NextScrollCursorCenterTopBottom::Center,
21757            "Default next scroll direction is center",
21758        );
21759
21760        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21761        assert_eq!(
21762            e.next_scroll_position,
21763            NextScrollCursorCenterTopBottom::Top,
21764            "After center, next scroll direction should be top",
21765        );
21766
21767        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21768        assert_eq!(
21769            e.next_scroll_position,
21770            NextScrollCursorCenterTopBottom::Bottom,
21771            "After top, next scroll direction should be bottom",
21772        );
21773
21774        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21775        assert_eq!(
21776            e.next_scroll_position,
21777            NextScrollCursorCenterTopBottom::Center,
21778            "After bottom, scrolling should start over",
21779        );
21780
21781        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21782        assert_eq!(
21783            e.next_scroll_position,
21784            NextScrollCursorCenterTopBottom::Top,
21785            "Scrolling continues if retriggered fast enough"
21786        );
21787    });
21788
21789    cx.executor()
21790        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
21791    cx.executor().run_until_parked();
21792    cx.update_editor(|e, _, _| {
21793        assert_eq!(
21794            e.next_scroll_position,
21795            NextScrollCursorCenterTopBottom::Center,
21796            "If scrolling is not triggered fast enough, it should reset"
21797        );
21798    });
21799}
21800
21801#[gpui::test]
21802async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
21803    init_test(cx, |_| {});
21804    let mut cx = EditorLspTestContext::new_rust(
21805        lsp::ServerCapabilities {
21806            definition_provider: Some(lsp::OneOf::Left(true)),
21807            references_provider: Some(lsp::OneOf::Left(true)),
21808            ..lsp::ServerCapabilities::default()
21809        },
21810        cx,
21811    )
21812    .await;
21813
21814    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
21815        let go_to_definition = cx
21816            .lsp
21817            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21818                move |params, _| async move {
21819                    if empty_go_to_definition {
21820                        Ok(None)
21821                    } else {
21822                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
21823                            uri: params.text_document_position_params.text_document.uri,
21824                            range: lsp::Range::new(
21825                                lsp::Position::new(4, 3),
21826                                lsp::Position::new(4, 6),
21827                            ),
21828                        })))
21829                    }
21830                },
21831            );
21832        let references = cx
21833            .lsp
21834            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21835                Ok(Some(vec![lsp::Location {
21836                    uri: params.text_document_position.text_document.uri,
21837                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
21838                }]))
21839            });
21840        (go_to_definition, references)
21841    };
21842
21843    cx.set_state(
21844        &r#"fn one() {
21845            let mut a = ˇtwo();
21846        }
21847
21848        fn two() {}"#
21849            .unindent(),
21850    );
21851    set_up_lsp_handlers(false, &mut cx);
21852    let navigated = cx
21853        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21854        .await
21855        .expect("Failed to navigate to definition");
21856    assert_eq!(
21857        navigated,
21858        Navigated::Yes,
21859        "Should have navigated to definition from the GetDefinition response"
21860    );
21861    cx.assert_editor_state(
21862        &r#"fn one() {
21863            let mut a = two();
21864        }
21865
21866        fn «twoˇ»() {}"#
21867            .unindent(),
21868    );
21869
21870    let editors = cx.update_workspace(|workspace, _, cx| {
21871        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21872    });
21873    cx.update_editor(|_, _, test_editor_cx| {
21874        assert_eq!(
21875            editors.len(),
21876            1,
21877            "Initially, only one, test, editor should be open in the workspace"
21878        );
21879        assert_eq!(
21880            test_editor_cx.entity(),
21881            editors.last().expect("Asserted len is 1").clone()
21882        );
21883    });
21884
21885    set_up_lsp_handlers(true, &mut cx);
21886    let navigated = cx
21887        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21888        .await
21889        .expect("Failed to navigate to lookup references");
21890    assert_eq!(
21891        navigated,
21892        Navigated::Yes,
21893        "Should have navigated to references as a fallback after empty GoToDefinition response"
21894    );
21895    // We should not change the selections in the existing file,
21896    // if opening another milti buffer with the references
21897    cx.assert_editor_state(
21898        &r#"fn one() {
21899            let mut a = two();
21900        }
21901
21902        fn «twoˇ»() {}"#
21903            .unindent(),
21904    );
21905    let editors = cx.update_workspace(|workspace, _, cx| {
21906        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21907    });
21908    cx.update_editor(|_, _, test_editor_cx| {
21909        assert_eq!(
21910            editors.len(),
21911            2,
21912            "After falling back to references search, we open a new editor with the results"
21913        );
21914        let references_fallback_text = editors
21915            .into_iter()
21916            .find(|new_editor| *new_editor != test_editor_cx.entity())
21917            .expect("Should have one non-test editor now")
21918            .read(test_editor_cx)
21919            .text(test_editor_cx);
21920        assert_eq!(
21921            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
21922            "Should use the range from the references response and not the GoToDefinition one"
21923        );
21924    });
21925}
21926
21927#[gpui::test]
21928async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
21929    init_test(cx, |_| {});
21930    cx.update(|cx| {
21931        let mut editor_settings = EditorSettings::get_global(cx).clone();
21932        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
21933        EditorSettings::override_global(editor_settings, cx);
21934    });
21935    let mut cx = EditorLspTestContext::new_rust(
21936        lsp::ServerCapabilities {
21937            definition_provider: Some(lsp::OneOf::Left(true)),
21938            references_provider: Some(lsp::OneOf::Left(true)),
21939            ..lsp::ServerCapabilities::default()
21940        },
21941        cx,
21942    )
21943    .await;
21944    let original_state = r#"fn one() {
21945        let mut a = ˇtwo();
21946    }
21947
21948    fn two() {}"#
21949        .unindent();
21950    cx.set_state(&original_state);
21951
21952    let mut go_to_definition = cx
21953        .lsp
21954        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21955            move |_, _| async move { Ok(None) },
21956        );
21957    let _references = cx
21958        .lsp
21959        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
21960            panic!("Should not call for references with no go to definition fallback")
21961        });
21962
21963    let navigated = cx
21964        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21965        .await
21966        .expect("Failed to navigate to lookup references");
21967    go_to_definition
21968        .next()
21969        .await
21970        .expect("Should have called the go_to_definition handler");
21971
21972    assert_eq!(
21973        navigated,
21974        Navigated::No,
21975        "Should have navigated to references as a fallback after empty GoToDefinition response"
21976    );
21977    cx.assert_editor_state(&original_state);
21978    let editors = cx.update_workspace(|workspace, _, cx| {
21979        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21980    });
21981    cx.update_editor(|_, _, _| {
21982        assert_eq!(
21983            editors.len(),
21984            1,
21985            "After unsuccessful fallback, no other editor should have been opened"
21986        );
21987    });
21988}
21989
21990#[gpui::test]
21991async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
21992    init_test(cx, |_| {});
21993    let mut cx = EditorLspTestContext::new_rust(
21994        lsp::ServerCapabilities {
21995            references_provider: Some(lsp::OneOf::Left(true)),
21996            ..lsp::ServerCapabilities::default()
21997        },
21998        cx,
21999    )
22000    .await;
22001
22002    cx.set_state(
22003        &r#"
22004        fn one() {
22005            let mut a = two();
22006        }
22007
22008        fn ˇtwo() {}"#
22009            .unindent(),
22010    );
22011    cx.lsp
22012        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
22013            Ok(Some(vec![
22014                lsp::Location {
22015                    uri: params.text_document_position.text_document.uri.clone(),
22016                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
22017                },
22018                lsp::Location {
22019                    uri: params.text_document_position.text_document.uri,
22020                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
22021                },
22022            ]))
22023        });
22024    let navigated = cx
22025        .update_editor(|editor, window, cx| {
22026            editor.find_all_references(&FindAllReferences, window, cx)
22027        })
22028        .unwrap()
22029        .await
22030        .expect("Failed to navigate to references");
22031    assert_eq!(
22032        navigated,
22033        Navigated::Yes,
22034        "Should have navigated to references from the FindAllReferences response"
22035    );
22036    cx.assert_editor_state(
22037        &r#"fn one() {
22038            let mut a = two();
22039        }
22040
22041        fn ˇtwo() {}"#
22042            .unindent(),
22043    );
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!(editors.len(), 2, "We should have opened a new multibuffer");
22050    });
22051
22052    cx.set_state(
22053        &r#"fn one() {
22054            let mut a = ˇtwo();
22055        }
22056
22057        fn two() {}"#
22058            .unindent(),
22059    );
22060    let navigated = cx
22061        .update_editor(|editor, window, cx| {
22062            editor.find_all_references(&FindAllReferences, window, cx)
22063        })
22064        .unwrap()
22065        .await
22066        .expect("Failed to navigate to references");
22067    assert_eq!(
22068        navigated,
22069        Navigated::Yes,
22070        "Should have navigated to references from the FindAllReferences response"
22071    );
22072    cx.assert_editor_state(
22073        &r#"fn one() {
22074            let mut a = ˇtwo();
22075        }
22076
22077        fn two() {}"#
22078            .unindent(),
22079    );
22080    let editors = cx.update_workspace(|workspace, _, cx| {
22081        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22082    });
22083    cx.update_editor(|_, _, _| {
22084        assert_eq!(
22085            editors.len(),
22086            2,
22087            "should have re-used the previous multibuffer"
22088        );
22089    });
22090
22091    cx.set_state(
22092        &r#"fn one() {
22093            let mut a = ˇtwo();
22094        }
22095        fn three() {}
22096        fn two() {}"#
22097            .unindent(),
22098    );
22099    cx.lsp
22100        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
22101            Ok(Some(vec![
22102                lsp::Location {
22103                    uri: params.text_document_position.text_document.uri.clone(),
22104                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
22105                },
22106                lsp::Location {
22107                    uri: params.text_document_position.text_document.uri,
22108                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
22109                },
22110            ]))
22111        });
22112    let navigated = cx
22113        .update_editor(|editor, window, cx| {
22114            editor.find_all_references(&FindAllReferences, window, cx)
22115        })
22116        .unwrap()
22117        .await
22118        .expect("Failed to navigate to references");
22119    assert_eq!(
22120        navigated,
22121        Navigated::Yes,
22122        "Should have navigated to references from the FindAllReferences response"
22123    );
22124    cx.assert_editor_state(
22125        &r#"fn one() {
22126                let mut a = ˇtwo();
22127            }
22128            fn three() {}
22129            fn two() {}"#
22130            .unindent(),
22131    );
22132    let editors = cx.update_workspace(|workspace, _, cx| {
22133        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22134    });
22135    cx.update_editor(|_, _, _| {
22136        assert_eq!(
22137            editors.len(),
22138            3,
22139            "should have used a new multibuffer as offsets changed"
22140        );
22141    });
22142}
22143#[gpui::test]
22144async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
22145    init_test(cx, |_| {});
22146
22147    let language = Arc::new(Language::new(
22148        LanguageConfig::default(),
22149        Some(tree_sitter_rust::LANGUAGE.into()),
22150    ));
22151
22152    let text = r#"
22153        #[cfg(test)]
22154        mod tests() {
22155            #[test]
22156            fn runnable_1() {
22157                let a = 1;
22158            }
22159
22160            #[test]
22161            fn runnable_2() {
22162                let a = 1;
22163                let b = 2;
22164            }
22165        }
22166    "#
22167    .unindent();
22168
22169    let fs = FakeFs::new(cx.executor());
22170    fs.insert_file("/file.rs", Default::default()).await;
22171
22172    let project = Project::test(fs, ["/a".as_ref()], cx).await;
22173    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22174    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22175    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
22176    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
22177
22178    let editor = cx.new_window_entity(|window, cx| {
22179        Editor::new(
22180            EditorMode::full(),
22181            multi_buffer,
22182            Some(project.clone()),
22183            window,
22184            cx,
22185        )
22186    });
22187
22188    editor.update_in(cx, |editor, window, cx| {
22189        let snapshot = editor.buffer().read(cx).snapshot(cx);
22190        editor.tasks.insert(
22191            (buffer.read(cx).remote_id(), 3),
22192            RunnableTasks {
22193                templates: vec![],
22194                offset: snapshot.anchor_before(43),
22195                column: 0,
22196                extra_variables: HashMap::default(),
22197                context_range: BufferOffset(43)..BufferOffset(85),
22198            },
22199        );
22200        editor.tasks.insert(
22201            (buffer.read(cx).remote_id(), 8),
22202            RunnableTasks {
22203                templates: vec![],
22204                offset: snapshot.anchor_before(86),
22205                column: 0,
22206                extra_variables: HashMap::default(),
22207                context_range: BufferOffset(86)..BufferOffset(191),
22208            },
22209        );
22210
22211        // Test finding task when cursor is inside function body
22212        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22213            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
22214        });
22215        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
22216        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
22217
22218        // Test finding task when cursor is on function name
22219        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22220            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
22221        });
22222        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
22223        assert_eq!(row, 8, "Should find task when cursor is on function name");
22224    });
22225}
22226
22227#[gpui::test]
22228async fn test_folding_buffers(cx: &mut TestAppContext) {
22229    init_test(cx, |_| {});
22230
22231    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
22232    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
22233    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
22234
22235    let fs = FakeFs::new(cx.executor());
22236    fs.insert_tree(
22237        path!("/a"),
22238        json!({
22239            "first.rs": sample_text_1,
22240            "second.rs": sample_text_2,
22241            "third.rs": sample_text_3,
22242        }),
22243    )
22244    .await;
22245    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22246    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22247    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22248    let worktree = project.update(cx, |project, cx| {
22249        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22250        assert_eq!(worktrees.len(), 1);
22251        worktrees.pop().unwrap()
22252    });
22253    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22254
22255    let buffer_1 = project
22256        .update(cx, |project, cx| {
22257            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
22258        })
22259        .await
22260        .unwrap();
22261    let buffer_2 = project
22262        .update(cx, |project, cx| {
22263            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
22264        })
22265        .await
22266        .unwrap();
22267    let buffer_3 = project
22268        .update(cx, |project, cx| {
22269            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
22270        })
22271        .await
22272        .unwrap();
22273
22274    let multi_buffer = cx.new(|cx| {
22275        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22276        multi_buffer.push_excerpts(
22277            buffer_1.clone(),
22278            [
22279                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22280                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22281                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22282            ],
22283            cx,
22284        );
22285        multi_buffer.push_excerpts(
22286            buffer_2.clone(),
22287            [
22288                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22289                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22290                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22291            ],
22292            cx,
22293        );
22294        multi_buffer.push_excerpts(
22295            buffer_3.clone(),
22296            [
22297                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22298                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22299                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22300            ],
22301            cx,
22302        );
22303        multi_buffer
22304    });
22305    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22306        Editor::new(
22307            EditorMode::full(),
22308            multi_buffer.clone(),
22309            Some(project.clone()),
22310            window,
22311            cx,
22312        )
22313    });
22314
22315    assert_eq!(
22316        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22317        "\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",
22318    );
22319
22320    multi_buffer_editor.update(cx, |editor, cx| {
22321        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
22322    });
22323    assert_eq!(
22324        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22325        "\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",
22326        "After folding the first buffer, its text should not be displayed"
22327    );
22328
22329    multi_buffer_editor.update(cx, |editor, cx| {
22330        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
22331    });
22332    assert_eq!(
22333        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22334        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
22335        "After folding the second buffer, its text should not be displayed"
22336    );
22337
22338    multi_buffer_editor.update(cx, |editor, cx| {
22339        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
22340    });
22341    assert_eq!(
22342        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22343        "\n\n\n\n\n",
22344        "After folding the third buffer, its text should not be displayed"
22345    );
22346
22347    // Emulate selection inside the fold logic, that should work
22348    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22349        editor
22350            .snapshot(window, cx)
22351            .next_line_boundary(Point::new(0, 4));
22352    });
22353
22354    multi_buffer_editor.update(cx, |editor, cx| {
22355        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
22356    });
22357    assert_eq!(
22358        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22359        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
22360        "After unfolding the second buffer, its text should be displayed"
22361    );
22362
22363    // Typing inside of buffer 1 causes that buffer to be unfolded.
22364    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22365        assert_eq!(
22366            multi_buffer
22367                .read(cx)
22368                .snapshot(cx)
22369                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
22370                .collect::<String>(),
22371            "bbbb"
22372        );
22373        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22374            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
22375        });
22376        editor.handle_input("B", window, cx);
22377    });
22378
22379    assert_eq!(
22380        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22381        "\n\naaaa\nBbbbb\ncccc\n\n\nffff\ngggg\n\n\njjjj\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
22382        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
22383    );
22384
22385    multi_buffer_editor.update(cx, |editor, cx| {
22386        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
22387    });
22388    assert_eq!(
22389        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22390        "\n\naaaa\nBbbbb\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",
22391        "After unfolding the all buffers, all original text should be displayed"
22392    );
22393}
22394
22395#[gpui::test]
22396async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
22397    init_test(cx, |_| {});
22398
22399    let sample_text_1 = "1111\n2222\n3333".to_string();
22400    let sample_text_2 = "4444\n5555\n6666".to_string();
22401    let sample_text_3 = "7777\n8888\n9999".to_string();
22402
22403    let fs = FakeFs::new(cx.executor());
22404    fs.insert_tree(
22405        path!("/a"),
22406        json!({
22407            "first.rs": sample_text_1,
22408            "second.rs": sample_text_2,
22409            "third.rs": sample_text_3,
22410        }),
22411    )
22412    .await;
22413    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22414    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22415    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22416    let worktree = project.update(cx, |project, cx| {
22417        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22418        assert_eq!(worktrees.len(), 1);
22419        worktrees.pop().unwrap()
22420    });
22421    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22422
22423    let buffer_1 = project
22424        .update(cx, |project, cx| {
22425            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
22426        })
22427        .await
22428        .unwrap();
22429    let buffer_2 = project
22430        .update(cx, |project, cx| {
22431            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
22432        })
22433        .await
22434        .unwrap();
22435    let buffer_3 = project
22436        .update(cx, |project, cx| {
22437            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
22438        })
22439        .await
22440        .unwrap();
22441
22442    let multi_buffer = cx.new(|cx| {
22443        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22444        multi_buffer.push_excerpts(
22445            buffer_1.clone(),
22446            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22447            cx,
22448        );
22449        multi_buffer.push_excerpts(
22450            buffer_2.clone(),
22451            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22452            cx,
22453        );
22454        multi_buffer.push_excerpts(
22455            buffer_3.clone(),
22456            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22457            cx,
22458        );
22459        multi_buffer
22460    });
22461
22462    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22463        Editor::new(
22464            EditorMode::full(),
22465            multi_buffer,
22466            Some(project.clone()),
22467            window,
22468            cx,
22469        )
22470    });
22471
22472    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
22473    assert_eq!(
22474        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22475        full_text,
22476    );
22477
22478    multi_buffer_editor.update(cx, |editor, cx| {
22479        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
22480    });
22481    assert_eq!(
22482        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22483        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
22484        "After folding the first buffer, its text should not be displayed"
22485    );
22486
22487    multi_buffer_editor.update(cx, |editor, cx| {
22488        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
22489    });
22490
22491    assert_eq!(
22492        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22493        "\n\n\n\n\n\n7777\n8888\n9999",
22494        "After folding the second buffer, its text should not be displayed"
22495    );
22496
22497    multi_buffer_editor.update(cx, |editor, cx| {
22498        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
22499    });
22500    assert_eq!(
22501        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22502        "\n\n\n\n\n",
22503        "After folding the third buffer, its text should not be displayed"
22504    );
22505
22506    multi_buffer_editor.update(cx, |editor, cx| {
22507        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
22508    });
22509    assert_eq!(
22510        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22511        "\n\n\n\n4444\n5555\n6666\n\n",
22512        "After unfolding the second buffer, its text should be displayed"
22513    );
22514
22515    multi_buffer_editor.update(cx, |editor, cx| {
22516        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
22517    });
22518    assert_eq!(
22519        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22520        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
22521        "After unfolding the first buffer, its text should be displayed"
22522    );
22523
22524    multi_buffer_editor.update(cx, |editor, cx| {
22525        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
22526    });
22527    assert_eq!(
22528        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22529        full_text,
22530        "After unfolding all buffers, all original text should be displayed"
22531    );
22532}
22533
22534#[gpui::test]
22535async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
22536    init_test(cx, |_| {});
22537
22538    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
22539
22540    let fs = FakeFs::new(cx.executor());
22541    fs.insert_tree(
22542        path!("/a"),
22543        json!({
22544            "main.rs": sample_text,
22545        }),
22546    )
22547    .await;
22548    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22549    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22550    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22551    let worktree = project.update(cx, |project, cx| {
22552        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22553        assert_eq!(worktrees.len(), 1);
22554        worktrees.pop().unwrap()
22555    });
22556    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22557
22558    let buffer_1 = project
22559        .update(cx, |project, cx| {
22560            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22561        })
22562        .await
22563        .unwrap();
22564
22565    let multi_buffer = cx.new(|cx| {
22566        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22567        multi_buffer.push_excerpts(
22568            buffer_1.clone(),
22569            [ExcerptRange::new(
22570                Point::new(0, 0)
22571                    ..Point::new(
22572                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
22573                        0,
22574                    ),
22575            )],
22576            cx,
22577        );
22578        multi_buffer
22579    });
22580    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22581        Editor::new(
22582            EditorMode::full(),
22583            multi_buffer,
22584            Some(project.clone()),
22585            window,
22586            cx,
22587        )
22588    });
22589
22590    let selection_range = Point::new(1, 0)..Point::new(2, 0);
22591    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22592        enum TestHighlight {}
22593        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
22594        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
22595        editor.highlight_text::<TestHighlight>(
22596            vec![highlight_range.clone()],
22597            HighlightStyle::color(Hsla::green()),
22598            cx,
22599        );
22600        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22601            s.select_ranges(Some(highlight_range))
22602        });
22603    });
22604
22605    let full_text = format!("\n\n{sample_text}");
22606    assert_eq!(
22607        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22608        full_text,
22609    );
22610}
22611
22612#[gpui::test]
22613async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
22614    init_test(cx, |_| {});
22615    cx.update(|cx| {
22616        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
22617            "keymaps/default-linux.json",
22618            cx,
22619        )
22620        .unwrap();
22621        cx.bind_keys(default_key_bindings);
22622    });
22623
22624    let (editor, cx) = cx.add_window_view(|window, cx| {
22625        let multi_buffer = MultiBuffer::build_multi(
22626            [
22627                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
22628                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
22629                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
22630                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
22631            ],
22632            cx,
22633        );
22634        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
22635
22636        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
22637        // fold all but the second buffer, so that we test navigating between two
22638        // adjacent folded buffers, as well as folded buffers at the start and
22639        // end the multibuffer
22640        editor.fold_buffer(buffer_ids[0], cx);
22641        editor.fold_buffer(buffer_ids[2], cx);
22642        editor.fold_buffer(buffer_ids[3], cx);
22643
22644        editor
22645    });
22646    cx.simulate_resize(size(px(1000.), px(1000.)));
22647
22648    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
22649    cx.assert_excerpts_with_selections(indoc! {"
22650        [EXCERPT]
22651        ˇ[FOLDED]
22652        [EXCERPT]
22653        a1
22654        b1
22655        [EXCERPT]
22656        [FOLDED]
22657        [EXCERPT]
22658        [FOLDED]
22659        "
22660    });
22661    cx.simulate_keystroke("down");
22662    cx.assert_excerpts_with_selections(indoc! {"
22663        [EXCERPT]
22664        [FOLDED]
22665        [EXCERPT]
22666        ˇa1
22667        b1
22668        [EXCERPT]
22669        [FOLDED]
22670        [EXCERPT]
22671        [FOLDED]
22672        "
22673    });
22674    cx.simulate_keystroke("down");
22675    cx.assert_excerpts_with_selections(indoc! {"
22676        [EXCERPT]
22677        [FOLDED]
22678        [EXCERPT]
22679        a1
22680        ˇb1
22681        [EXCERPT]
22682        [FOLDED]
22683        [EXCERPT]
22684        [FOLDED]
22685        "
22686    });
22687    cx.simulate_keystroke("down");
22688    cx.assert_excerpts_with_selections(indoc! {"
22689        [EXCERPT]
22690        [FOLDED]
22691        [EXCERPT]
22692        a1
22693        b1
22694        ˇ[EXCERPT]
22695        [FOLDED]
22696        [EXCERPT]
22697        [FOLDED]
22698        "
22699    });
22700    cx.simulate_keystroke("down");
22701    cx.assert_excerpts_with_selections(indoc! {"
22702        [EXCERPT]
22703        [FOLDED]
22704        [EXCERPT]
22705        a1
22706        b1
22707        [EXCERPT]
22708        ˇ[FOLDED]
22709        [EXCERPT]
22710        [FOLDED]
22711        "
22712    });
22713    for _ in 0..5 {
22714        cx.simulate_keystroke("down");
22715        cx.assert_excerpts_with_selections(indoc! {"
22716            [EXCERPT]
22717            [FOLDED]
22718            [EXCERPT]
22719            a1
22720            b1
22721            [EXCERPT]
22722            [FOLDED]
22723            [EXCERPT]
22724            ˇ[FOLDED]
22725            "
22726        });
22727    }
22728
22729    cx.simulate_keystroke("up");
22730    cx.assert_excerpts_with_selections(indoc! {"
22731        [EXCERPT]
22732        [FOLDED]
22733        [EXCERPT]
22734        a1
22735        b1
22736        [EXCERPT]
22737        ˇ[FOLDED]
22738        [EXCERPT]
22739        [FOLDED]
22740        "
22741    });
22742    cx.simulate_keystroke("up");
22743    cx.assert_excerpts_with_selections(indoc! {"
22744        [EXCERPT]
22745        [FOLDED]
22746        [EXCERPT]
22747        a1
22748        b1
22749        ˇ[EXCERPT]
22750        [FOLDED]
22751        [EXCERPT]
22752        [FOLDED]
22753        "
22754    });
22755    cx.simulate_keystroke("up");
22756    cx.assert_excerpts_with_selections(indoc! {"
22757        [EXCERPT]
22758        [FOLDED]
22759        [EXCERPT]
22760        a1
22761        ˇb1
22762        [EXCERPT]
22763        [FOLDED]
22764        [EXCERPT]
22765        [FOLDED]
22766        "
22767    });
22768    cx.simulate_keystroke("up");
22769    cx.assert_excerpts_with_selections(indoc! {"
22770        [EXCERPT]
22771        [FOLDED]
22772        [EXCERPT]
22773        ˇa1
22774        b1
22775        [EXCERPT]
22776        [FOLDED]
22777        [EXCERPT]
22778        [FOLDED]
22779        "
22780    });
22781    for _ in 0..5 {
22782        cx.simulate_keystroke("up");
22783        cx.assert_excerpts_with_selections(indoc! {"
22784            [EXCERPT]
22785            ˇ[FOLDED]
22786            [EXCERPT]
22787            a1
22788            b1
22789            [EXCERPT]
22790            [FOLDED]
22791            [EXCERPT]
22792            [FOLDED]
22793            "
22794        });
22795    }
22796}
22797
22798#[gpui::test]
22799async fn test_edit_prediction_text(cx: &mut TestAppContext) {
22800    init_test(cx, |_| {});
22801
22802    // Simple insertion
22803    assert_highlighted_edits(
22804        "Hello, world!",
22805        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
22806        true,
22807        cx,
22808        |highlighted_edits, cx| {
22809            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
22810            assert_eq!(highlighted_edits.highlights.len(), 1);
22811            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
22812            assert_eq!(
22813                highlighted_edits.highlights[0].1.background_color,
22814                Some(cx.theme().status().created_background)
22815            );
22816        },
22817    )
22818    .await;
22819
22820    // Replacement
22821    assert_highlighted_edits(
22822        "This is a test.",
22823        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
22824        false,
22825        cx,
22826        |highlighted_edits, cx| {
22827            assert_eq!(highlighted_edits.text, "That is a test.");
22828            assert_eq!(highlighted_edits.highlights.len(), 1);
22829            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
22830            assert_eq!(
22831                highlighted_edits.highlights[0].1.background_color,
22832                Some(cx.theme().status().created_background)
22833            );
22834        },
22835    )
22836    .await;
22837
22838    // Multiple edits
22839    assert_highlighted_edits(
22840        "Hello, world!",
22841        vec![
22842            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
22843            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
22844        ],
22845        false,
22846        cx,
22847        |highlighted_edits, cx| {
22848            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
22849            assert_eq!(highlighted_edits.highlights.len(), 2);
22850            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
22851            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
22852            assert_eq!(
22853                highlighted_edits.highlights[0].1.background_color,
22854                Some(cx.theme().status().created_background)
22855            );
22856            assert_eq!(
22857                highlighted_edits.highlights[1].1.background_color,
22858                Some(cx.theme().status().created_background)
22859            );
22860        },
22861    )
22862    .await;
22863
22864    // Multiple lines with edits
22865    assert_highlighted_edits(
22866        "First line\nSecond line\nThird line\nFourth line",
22867        vec![
22868            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
22869            (
22870                Point::new(2, 0)..Point::new(2, 10),
22871                "New third line".to_string(),
22872            ),
22873            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
22874        ],
22875        false,
22876        cx,
22877        |highlighted_edits, cx| {
22878            assert_eq!(
22879                highlighted_edits.text,
22880                "Second modified\nNew third line\nFourth updated line"
22881            );
22882            assert_eq!(highlighted_edits.highlights.len(), 3);
22883            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
22884            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
22885            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
22886            for highlight in &highlighted_edits.highlights {
22887                assert_eq!(
22888                    highlight.1.background_color,
22889                    Some(cx.theme().status().created_background)
22890                );
22891            }
22892        },
22893    )
22894    .await;
22895}
22896
22897#[gpui::test]
22898async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
22899    init_test(cx, |_| {});
22900
22901    // Deletion
22902    assert_highlighted_edits(
22903        "Hello, world!",
22904        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
22905        true,
22906        cx,
22907        |highlighted_edits, cx| {
22908            assert_eq!(highlighted_edits.text, "Hello, world!");
22909            assert_eq!(highlighted_edits.highlights.len(), 1);
22910            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
22911            assert_eq!(
22912                highlighted_edits.highlights[0].1.background_color,
22913                Some(cx.theme().status().deleted_background)
22914            );
22915        },
22916    )
22917    .await;
22918
22919    // Insertion
22920    assert_highlighted_edits(
22921        "Hello, world!",
22922        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
22923        true,
22924        cx,
22925        |highlighted_edits, cx| {
22926            assert_eq!(highlighted_edits.highlights.len(), 1);
22927            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
22928            assert_eq!(
22929                highlighted_edits.highlights[0].1.background_color,
22930                Some(cx.theme().status().created_background)
22931            );
22932        },
22933    )
22934    .await;
22935}
22936
22937async fn assert_highlighted_edits(
22938    text: &str,
22939    edits: Vec<(Range<Point>, String)>,
22940    include_deletions: bool,
22941    cx: &mut TestAppContext,
22942    assertion_fn: impl Fn(HighlightedText, &App),
22943) {
22944    let window = cx.add_window(|window, cx| {
22945        let buffer = MultiBuffer::build_simple(text, cx);
22946        Editor::new(EditorMode::full(), buffer, None, window, cx)
22947    });
22948    let cx = &mut VisualTestContext::from_window(*window, cx);
22949
22950    let (buffer, snapshot) = window
22951        .update(cx, |editor, _window, cx| {
22952            (
22953                editor.buffer().clone(),
22954                editor.buffer().read(cx).snapshot(cx),
22955            )
22956        })
22957        .unwrap();
22958
22959    let edits = edits
22960        .into_iter()
22961        .map(|(range, edit)| {
22962            (
22963                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
22964                edit,
22965            )
22966        })
22967        .collect::<Vec<_>>();
22968
22969    let text_anchor_edits = edits
22970        .clone()
22971        .into_iter()
22972        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit.into()))
22973        .collect::<Vec<_>>();
22974
22975    let edit_preview = window
22976        .update(cx, |_, _window, cx| {
22977            buffer
22978                .read(cx)
22979                .as_singleton()
22980                .unwrap()
22981                .read(cx)
22982                .preview_edits(text_anchor_edits.into(), cx)
22983        })
22984        .unwrap()
22985        .await;
22986
22987    cx.update(|_window, cx| {
22988        let highlighted_edits = edit_prediction_edit_text(
22989            snapshot.as_singleton().unwrap().2,
22990            &edits,
22991            &edit_preview,
22992            include_deletions,
22993            cx,
22994        );
22995        assertion_fn(highlighted_edits, cx)
22996    });
22997}
22998
22999#[track_caller]
23000fn assert_breakpoint(
23001    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
23002    path: &Arc<Path>,
23003    expected: Vec<(u32, Breakpoint)>,
23004) {
23005    if expected.is_empty() {
23006        assert!(!breakpoints.contains_key(path), "{}", path.display());
23007    } else {
23008        let mut breakpoint = breakpoints
23009            .get(path)
23010            .unwrap()
23011            .iter()
23012            .map(|breakpoint| {
23013                (
23014                    breakpoint.row,
23015                    Breakpoint {
23016                        message: breakpoint.message.clone(),
23017                        state: breakpoint.state,
23018                        condition: breakpoint.condition.clone(),
23019                        hit_condition: breakpoint.hit_condition.clone(),
23020                    },
23021                )
23022            })
23023            .collect::<Vec<_>>();
23024
23025        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
23026
23027        assert_eq!(expected, breakpoint);
23028    }
23029}
23030
23031fn add_log_breakpoint_at_cursor(
23032    editor: &mut Editor,
23033    log_message: &str,
23034    window: &mut Window,
23035    cx: &mut Context<Editor>,
23036) {
23037    let (anchor, bp) = editor
23038        .breakpoints_at_cursors(window, cx)
23039        .first()
23040        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
23041        .unwrap_or_else(|| {
23042            let snapshot = editor.snapshot(window, cx);
23043            let cursor_position: Point =
23044                editor.selections.newest(&snapshot.display_snapshot).head();
23045
23046            let breakpoint_position = snapshot
23047                .buffer_snapshot()
23048                .anchor_before(Point::new(cursor_position.row, 0));
23049
23050            (breakpoint_position, Breakpoint::new_log(log_message))
23051        });
23052
23053    editor.edit_breakpoint_at_anchor(
23054        anchor,
23055        bp,
23056        BreakpointEditAction::EditLogMessage(log_message.into()),
23057        cx,
23058    );
23059}
23060
23061#[gpui::test]
23062async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
23063    init_test(cx, |_| {});
23064
23065    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23066    let fs = FakeFs::new(cx.executor());
23067    fs.insert_tree(
23068        path!("/a"),
23069        json!({
23070            "main.rs": sample_text,
23071        }),
23072    )
23073    .await;
23074    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23075    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23076    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23077
23078    let fs = FakeFs::new(cx.executor());
23079    fs.insert_tree(
23080        path!("/a"),
23081        json!({
23082            "main.rs": sample_text,
23083        }),
23084    )
23085    .await;
23086    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23087    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23088    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23089    let worktree_id = workspace
23090        .update(cx, |workspace, _window, cx| {
23091            workspace.project().update(cx, |project, cx| {
23092                project.worktrees(cx).next().unwrap().read(cx).id()
23093            })
23094        })
23095        .unwrap();
23096
23097    let buffer = project
23098        .update(cx, |project, cx| {
23099            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23100        })
23101        .await
23102        .unwrap();
23103
23104    let (editor, cx) = cx.add_window_view(|window, cx| {
23105        Editor::new(
23106            EditorMode::full(),
23107            MultiBuffer::build_from_buffer(buffer, cx),
23108            Some(project.clone()),
23109            window,
23110            cx,
23111        )
23112    });
23113
23114    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23115    let abs_path = project.read_with(cx, |project, cx| {
23116        project
23117            .absolute_path(&project_path, cx)
23118            .map(Arc::from)
23119            .unwrap()
23120    });
23121
23122    // assert we can add breakpoint on the first line
23123    editor.update_in(cx, |editor, window, cx| {
23124        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23125        editor.move_to_end(&MoveToEnd, window, cx);
23126        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23127    });
23128
23129    let breakpoints = editor.update(cx, |editor, cx| {
23130        editor
23131            .breakpoint_store()
23132            .as_ref()
23133            .unwrap()
23134            .read(cx)
23135            .all_source_breakpoints(cx)
23136    });
23137
23138    assert_eq!(1, breakpoints.len());
23139    assert_breakpoint(
23140        &breakpoints,
23141        &abs_path,
23142        vec![
23143            (0, Breakpoint::new_standard()),
23144            (3, Breakpoint::new_standard()),
23145        ],
23146    );
23147
23148    editor.update_in(cx, |editor, window, cx| {
23149        editor.move_to_beginning(&MoveToBeginning, window, cx);
23150        editor.toggle_breakpoint(&actions::ToggleBreakpoint, 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_eq!(1, breakpoints.len());
23163    assert_breakpoint(
23164        &breakpoints,
23165        &abs_path,
23166        vec![(3, Breakpoint::new_standard())],
23167    );
23168
23169    editor.update_in(cx, |editor, window, cx| {
23170        editor.move_to_end(&MoveToEnd, window, cx);
23171        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23172    });
23173
23174    let breakpoints = editor.update(cx, |editor, cx| {
23175        editor
23176            .breakpoint_store()
23177            .as_ref()
23178            .unwrap()
23179            .read(cx)
23180            .all_source_breakpoints(cx)
23181    });
23182
23183    assert_eq!(0, breakpoints.len());
23184    assert_breakpoint(&breakpoints, &abs_path, vec![]);
23185}
23186
23187#[gpui::test]
23188async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
23189    init_test(cx, |_| {});
23190
23191    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23192
23193    let fs = FakeFs::new(cx.executor());
23194    fs.insert_tree(
23195        path!("/a"),
23196        json!({
23197            "main.rs": sample_text,
23198        }),
23199    )
23200    .await;
23201    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23202    let (workspace, cx) =
23203        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23204
23205    let worktree_id = workspace.update(cx, |workspace, cx| {
23206        workspace.project().update(cx, |project, cx| {
23207            project.worktrees(cx).next().unwrap().read(cx).id()
23208        })
23209    });
23210
23211    let buffer = project
23212        .update(cx, |project, cx| {
23213            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23214        })
23215        .await
23216        .unwrap();
23217
23218    let (editor, cx) = cx.add_window_view(|window, cx| {
23219        Editor::new(
23220            EditorMode::full(),
23221            MultiBuffer::build_from_buffer(buffer, cx),
23222            Some(project.clone()),
23223            window,
23224            cx,
23225        )
23226    });
23227
23228    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23229    let abs_path = project.read_with(cx, |project, cx| {
23230        project
23231            .absolute_path(&project_path, cx)
23232            .map(Arc::from)
23233            .unwrap()
23234    });
23235
23236    editor.update_in(cx, |editor, window, cx| {
23237        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
23238    });
23239
23240    let breakpoints = editor.update(cx, |editor, cx| {
23241        editor
23242            .breakpoint_store()
23243            .as_ref()
23244            .unwrap()
23245            .read(cx)
23246            .all_source_breakpoints(cx)
23247    });
23248
23249    assert_breakpoint(
23250        &breakpoints,
23251        &abs_path,
23252        vec![(0, Breakpoint::new_log("hello world"))],
23253    );
23254
23255    // Removing a log message from a log breakpoint should remove it
23256    editor.update_in(cx, |editor, window, cx| {
23257        add_log_breakpoint_at_cursor(editor, "", window, cx);
23258    });
23259
23260    let breakpoints = editor.update(cx, |editor, cx| {
23261        editor
23262            .breakpoint_store()
23263            .as_ref()
23264            .unwrap()
23265            .read(cx)
23266            .all_source_breakpoints(cx)
23267    });
23268
23269    assert_breakpoint(&breakpoints, &abs_path, vec![]);
23270
23271    editor.update_in(cx, |editor, window, cx| {
23272        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23273        editor.move_to_end(&MoveToEnd, window, cx);
23274        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23275        // Not adding a log message to a standard breakpoint shouldn't remove it
23276        add_log_breakpoint_at_cursor(editor, "", window, cx);
23277    });
23278
23279    let breakpoints = editor.update(cx, |editor, cx| {
23280        editor
23281            .breakpoint_store()
23282            .as_ref()
23283            .unwrap()
23284            .read(cx)
23285            .all_source_breakpoints(cx)
23286    });
23287
23288    assert_breakpoint(
23289        &breakpoints,
23290        &abs_path,
23291        vec![
23292            (0, Breakpoint::new_standard()),
23293            (3, Breakpoint::new_standard()),
23294        ],
23295    );
23296
23297    editor.update_in(cx, |editor, window, cx| {
23298        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
23299    });
23300
23301    let breakpoints = editor.update(cx, |editor, cx| {
23302        editor
23303            .breakpoint_store()
23304            .as_ref()
23305            .unwrap()
23306            .read(cx)
23307            .all_source_breakpoints(cx)
23308    });
23309
23310    assert_breakpoint(
23311        &breakpoints,
23312        &abs_path,
23313        vec![
23314            (0, Breakpoint::new_standard()),
23315            (3, Breakpoint::new_log("hello world")),
23316        ],
23317    );
23318
23319    editor.update_in(cx, |editor, window, cx| {
23320        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
23321    });
23322
23323    let breakpoints = editor.update(cx, |editor, cx| {
23324        editor
23325            .breakpoint_store()
23326            .as_ref()
23327            .unwrap()
23328            .read(cx)
23329            .all_source_breakpoints(cx)
23330    });
23331
23332    assert_breakpoint(
23333        &breakpoints,
23334        &abs_path,
23335        vec![
23336            (0, Breakpoint::new_standard()),
23337            (3, Breakpoint::new_log("hello Earth!!")),
23338        ],
23339    );
23340}
23341
23342/// This also tests that Editor::breakpoint_at_cursor_head is working properly
23343/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
23344/// or when breakpoints were placed out of order. This tests for a regression too
23345#[gpui::test]
23346async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
23347    init_test(cx, |_| {});
23348
23349    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23350    let fs = FakeFs::new(cx.executor());
23351    fs.insert_tree(
23352        path!("/a"),
23353        json!({
23354            "main.rs": sample_text,
23355        }),
23356    )
23357    .await;
23358    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23359    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23360    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23361
23362    let fs = FakeFs::new(cx.executor());
23363    fs.insert_tree(
23364        path!("/a"),
23365        json!({
23366            "main.rs": sample_text,
23367        }),
23368    )
23369    .await;
23370    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23371    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23372    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23373    let worktree_id = workspace
23374        .update(cx, |workspace, _window, cx| {
23375            workspace.project().update(cx, |project, cx| {
23376                project.worktrees(cx).next().unwrap().read(cx).id()
23377            })
23378        })
23379        .unwrap();
23380
23381    let buffer = project
23382        .update(cx, |project, cx| {
23383            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23384        })
23385        .await
23386        .unwrap();
23387
23388    let (editor, cx) = cx.add_window_view(|window, cx| {
23389        Editor::new(
23390            EditorMode::full(),
23391            MultiBuffer::build_from_buffer(buffer, cx),
23392            Some(project.clone()),
23393            window,
23394            cx,
23395        )
23396    });
23397
23398    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23399    let abs_path = project.read_with(cx, |project, cx| {
23400        project
23401            .absolute_path(&project_path, cx)
23402            .map(Arc::from)
23403            .unwrap()
23404    });
23405
23406    // assert we can add breakpoint on the first line
23407    editor.update_in(cx, |editor, window, cx| {
23408        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23409        editor.move_to_end(&MoveToEnd, window, cx);
23410        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23411        editor.move_up(&MoveUp, window, cx);
23412        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23413    });
23414
23415    let breakpoints = editor.update(cx, |editor, cx| {
23416        editor
23417            .breakpoint_store()
23418            .as_ref()
23419            .unwrap()
23420            .read(cx)
23421            .all_source_breakpoints(cx)
23422    });
23423
23424    assert_eq!(1, breakpoints.len());
23425    assert_breakpoint(
23426        &breakpoints,
23427        &abs_path,
23428        vec![
23429            (0, Breakpoint::new_standard()),
23430            (2, Breakpoint::new_standard()),
23431            (3, Breakpoint::new_standard()),
23432        ],
23433    );
23434
23435    editor.update_in(cx, |editor, window, cx| {
23436        editor.move_to_beginning(&MoveToBeginning, window, cx);
23437        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23438        editor.move_to_end(&MoveToEnd, window, cx);
23439        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23440        // Disabling a breakpoint that doesn't exist should do nothing
23441        editor.move_up(&MoveUp, window, cx);
23442        editor.move_up(&MoveUp, window, cx);
23443        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23444    });
23445
23446    let breakpoints = editor.update(cx, |editor, cx| {
23447        editor
23448            .breakpoint_store()
23449            .as_ref()
23450            .unwrap()
23451            .read(cx)
23452            .all_source_breakpoints(cx)
23453    });
23454
23455    let disable_breakpoint = {
23456        let mut bp = Breakpoint::new_standard();
23457        bp.state = BreakpointState::Disabled;
23458        bp
23459    };
23460
23461    assert_eq!(1, breakpoints.len());
23462    assert_breakpoint(
23463        &breakpoints,
23464        &abs_path,
23465        vec![
23466            (0, disable_breakpoint.clone()),
23467            (2, Breakpoint::new_standard()),
23468            (3, disable_breakpoint.clone()),
23469        ],
23470    );
23471
23472    editor.update_in(cx, |editor, window, cx| {
23473        editor.move_to_beginning(&MoveToBeginning, window, cx);
23474        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23475        editor.move_to_end(&MoveToEnd, window, cx);
23476        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23477        editor.move_up(&MoveUp, window, cx);
23478        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23479    });
23480
23481    let breakpoints = editor.update(cx, |editor, cx| {
23482        editor
23483            .breakpoint_store()
23484            .as_ref()
23485            .unwrap()
23486            .read(cx)
23487            .all_source_breakpoints(cx)
23488    });
23489
23490    assert_eq!(1, breakpoints.len());
23491    assert_breakpoint(
23492        &breakpoints,
23493        &abs_path,
23494        vec![
23495            (0, Breakpoint::new_standard()),
23496            (2, disable_breakpoint),
23497            (3, Breakpoint::new_standard()),
23498        ],
23499    );
23500}
23501
23502#[gpui::test]
23503async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
23504    init_test(cx, |_| {});
23505    let capabilities = lsp::ServerCapabilities {
23506        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
23507            prepare_provider: Some(true),
23508            work_done_progress_options: Default::default(),
23509        })),
23510        ..Default::default()
23511    };
23512    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23513
23514    cx.set_state(indoc! {"
23515        struct Fˇoo {}
23516    "});
23517
23518    cx.update_editor(|editor, _, cx| {
23519        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23520        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23521        editor.highlight_background::<DocumentHighlightRead>(
23522            &[highlight_range],
23523            |theme| theme.colors().editor_document_highlight_read_background,
23524            cx,
23525        );
23526    });
23527
23528    let mut prepare_rename_handler = cx
23529        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
23530            move |_, _, _| async move {
23531                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
23532                    start: lsp::Position {
23533                        line: 0,
23534                        character: 7,
23535                    },
23536                    end: lsp::Position {
23537                        line: 0,
23538                        character: 10,
23539                    },
23540                })))
23541            },
23542        );
23543    let prepare_rename_task = cx
23544        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23545        .expect("Prepare rename was not started");
23546    prepare_rename_handler.next().await.unwrap();
23547    prepare_rename_task.await.expect("Prepare rename failed");
23548
23549    let mut rename_handler =
23550        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23551            let edit = lsp::TextEdit {
23552                range: lsp::Range {
23553                    start: lsp::Position {
23554                        line: 0,
23555                        character: 7,
23556                    },
23557                    end: lsp::Position {
23558                        line: 0,
23559                        character: 10,
23560                    },
23561                },
23562                new_text: "FooRenamed".to_string(),
23563            };
23564            Ok(Some(lsp::WorkspaceEdit::new(
23565                // Specify the same edit twice
23566                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
23567            )))
23568        });
23569    let rename_task = cx
23570        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23571        .expect("Confirm rename was not started");
23572    rename_handler.next().await.unwrap();
23573    rename_task.await.expect("Confirm rename failed");
23574    cx.run_until_parked();
23575
23576    // Despite two edits, only one is actually applied as those are identical
23577    cx.assert_editor_state(indoc! {"
23578        struct FooRenamedˇ {}
23579    "});
23580}
23581
23582#[gpui::test]
23583async fn test_rename_without_prepare(cx: &mut TestAppContext) {
23584    init_test(cx, |_| {});
23585    // These capabilities indicate that the server does not support prepare rename.
23586    let capabilities = lsp::ServerCapabilities {
23587        rename_provider: Some(lsp::OneOf::Left(true)),
23588        ..Default::default()
23589    };
23590    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23591
23592    cx.set_state(indoc! {"
23593        struct Fˇoo {}
23594    "});
23595
23596    cx.update_editor(|editor, _window, cx| {
23597        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23598        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23599        editor.highlight_background::<DocumentHighlightRead>(
23600            &[highlight_range],
23601            |theme| theme.colors().editor_document_highlight_read_background,
23602            cx,
23603        );
23604    });
23605
23606    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23607        .expect("Prepare rename was not started")
23608        .await
23609        .expect("Prepare rename failed");
23610
23611    let mut rename_handler =
23612        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23613            let edit = lsp::TextEdit {
23614                range: lsp::Range {
23615                    start: lsp::Position {
23616                        line: 0,
23617                        character: 7,
23618                    },
23619                    end: lsp::Position {
23620                        line: 0,
23621                        character: 10,
23622                    },
23623                },
23624                new_text: "FooRenamed".to_string(),
23625            };
23626            Ok(Some(lsp::WorkspaceEdit::new(
23627                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
23628            )))
23629        });
23630    let rename_task = cx
23631        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23632        .expect("Confirm rename was not started");
23633    rename_handler.next().await.unwrap();
23634    rename_task.await.expect("Confirm rename failed");
23635    cx.run_until_parked();
23636
23637    // Correct range is renamed, as `surrounding_word` is used to find it.
23638    cx.assert_editor_state(indoc! {"
23639        struct FooRenamedˇ {}
23640    "});
23641}
23642
23643#[gpui::test]
23644async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
23645    init_test(cx, |_| {});
23646    let mut cx = EditorTestContext::new(cx).await;
23647
23648    let language = Arc::new(
23649        Language::new(
23650            LanguageConfig::default(),
23651            Some(tree_sitter_html::LANGUAGE.into()),
23652        )
23653        .with_brackets_query(
23654            r#"
23655            ("<" @open "/>" @close)
23656            ("</" @open ">" @close)
23657            ("<" @open ">" @close)
23658            ("\"" @open "\"" @close)
23659            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
23660        "#,
23661        )
23662        .unwrap(),
23663    );
23664    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23665
23666    cx.set_state(indoc! {"
23667        <span>ˇ</span>
23668    "});
23669    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23670    cx.assert_editor_state(indoc! {"
23671        <span>
23672        ˇ
23673        </span>
23674    "});
23675
23676    cx.set_state(indoc! {"
23677        <span><span></span>ˇ</span>
23678    "});
23679    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23680    cx.assert_editor_state(indoc! {"
23681        <span><span></span>
23682        ˇ</span>
23683    "});
23684
23685    cx.set_state(indoc! {"
23686        <span>ˇ
23687        </span>
23688    "});
23689    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23690    cx.assert_editor_state(indoc! {"
23691        <span>
23692        ˇ
23693        </span>
23694    "});
23695}
23696
23697#[gpui::test(iterations = 10)]
23698async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
23699    init_test(cx, |_| {});
23700
23701    let fs = FakeFs::new(cx.executor());
23702    fs.insert_tree(
23703        path!("/dir"),
23704        json!({
23705            "a.ts": "a",
23706        }),
23707    )
23708    .await;
23709
23710    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
23711    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23712    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23713
23714    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23715    language_registry.add(Arc::new(Language::new(
23716        LanguageConfig {
23717            name: "TypeScript".into(),
23718            matcher: LanguageMatcher {
23719                path_suffixes: vec!["ts".to_string()],
23720                ..Default::default()
23721            },
23722            ..Default::default()
23723        },
23724        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
23725    )));
23726    let mut fake_language_servers = language_registry.register_fake_lsp(
23727        "TypeScript",
23728        FakeLspAdapter {
23729            capabilities: lsp::ServerCapabilities {
23730                code_lens_provider: Some(lsp::CodeLensOptions {
23731                    resolve_provider: Some(true),
23732                }),
23733                execute_command_provider: Some(lsp::ExecuteCommandOptions {
23734                    commands: vec!["_the/command".to_string()],
23735                    ..lsp::ExecuteCommandOptions::default()
23736                }),
23737                ..lsp::ServerCapabilities::default()
23738            },
23739            ..FakeLspAdapter::default()
23740        },
23741    );
23742
23743    let editor = workspace
23744        .update(cx, |workspace, window, cx| {
23745            workspace.open_abs_path(
23746                PathBuf::from(path!("/dir/a.ts")),
23747                OpenOptions::default(),
23748                window,
23749                cx,
23750            )
23751        })
23752        .unwrap()
23753        .await
23754        .unwrap()
23755        .downcast::<Editor>()
23756        .unwrap();
23757    cx.executor().run_until_parked();
23758
23759    let fake_server = fake_language_servers.next().await.unwrap();
23760
23761    let buffer = editor.update(cx, |editor, cx| {
23762        editor
23763            .buffer()
23764            .read(cx)
23765            .as_singleton()
23766            .expect("have opened a single file by path")
23767    });
23768
23769    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
23770    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
23771    drop(buffer_snapshot);
23772    let actions = cx
23773        .update_window(*workspace, |_, window, cx| {
23774            project.code_actions(&buffer, anchor..anchor, window, cx)
23775        })
23776        .unwrap();
23777
23778    fake_server
23779        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23780            Ok(Some(vec![
23781                lsp::CodeLens {
23782                    range: lsp::Range::default(),
23783                    command: Some(lsp::Command {
23784                        title: "Code lens command".to_owned(),
23785                        command: "_the/command".to_owned(),
23786                        arguments: None,
23787                    }),
23788                    data: None,
23789                },
23790                lsp::CodeLens {
23791                    range: lsp::Range::default(),
23792                    command: Some(lsp::Command {
23793                        title: "Command not in capabilities".to_owned(),
23794                        command: "not in capabilities".to_owned(),
23795                        arguments: None,
23796                    }),
23797                    data: None,
23798                },
23799                lsp::CodeLens {
23800                    range: lsp::Range {
23801                        start: lsp::Position {
23802                            line: 1,
23803                            character: 1,
23804                        },
23805                        end: lsp::Position {
23806                            line: 1,
23807                            character: 1,
23808                        },
23809                    },
23810                    command: Some(lsp::Command {
23811                        title: "Command not in range".to_owned(),
23812                        command: "_the/command".to_owned(),
23813                        arguments: None,
23814                    }),
23815                    data: None,
23816                },
23817            ]))
23818        })
23819        .next()
23820        .await;
23821
23822    let actions = actions.await.unwrap();
23823    assert_eq!(
23824        actions.len(),
23825        1,
23826        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
23827    );
23828    let action = actions[0].clone();
23829    let apply = project.update(cx, |project, cx| {
23830        project.apply_code_action(buffer.clone(), action, true, cx)
23831    });
23832
23833    // Resolving the code action does not populate its edits. In absence of
23834    // edits, we must execute the given command.
23835    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
23836        |mut lens, _| async move {
23837            let lens_command = lens.command.as_mut().expect("should have a command");
23838            assert_eq!(lens_command.title, "Code lens command");
23839            lens_command.arguments = Some(vec![json!("the-argument")]);
23840            Ok(lens)
23841        },
23842    );
23843
23844    // While executing the command, the language server sends the editor
23845    // a `workspaceEdit` request.
23846    fake_server
23847        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
23848            let fake = fake_server.clone();
23849            move |params, _| {
23850                assert_eq!(params.command, "_the/command");
23851                let fake = fake.clone();
23852                async move {
23853                    fake.server
23854                        .request::<lsp::request::ApplyWorkspaceEdit>(
23855                            lsp::ApplyWorkspaceEditParams {
23856                                label: None,
23857                                edit: lsp::WorkspaceEdit {
23858                                    changes: Some(
23859                                        [(
23860                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
23861                                            vec![lsp::TextEdit {
23862                                                range: lsp::Range::new(
23863                                                    lsp::Position::new(0, 0),
23864                                                    lsp::Position::new(0, 0),
23865                                                ),
23866                                                new_text: "X".into(),
23867                                            }],
23868                                        )]
23869                                        .into_iter()
23870                                        .collect(),
23871                                    ),
23872                                    ..lsp::WorkspaceEdit::default()
23873                                },
23874                            },
23875                        )
23876                        .await
23877                        .into_response()
23878                        .unwrap();
23879                    Ok(Some(json!(null)))
23880                }
23881            }
23882        })
23883        .next()
23884        .await;
23885
23886    // Applying the code lens command returns a project transaction containing the edits
23887    // sent by the language server in its `workspaceEdit` request.
23888    let transaction = apply.await.unwrap();
23889    assert!(transaction.0.contains_key(&buffer));
23890    buffer.update(cx, |buffer, cx| {
23891        assert_eq!(buffer.text(), "Xa");
23892        buffer.undo(cx);
23893        assert_eq!(buffer.text(), "a");
23894    });
23895
23896    let actions_after_edits = cx
23897        .update_window(*workspace, |_, window, cx| {
23898            project.code_actions(&buffer, anchor..anchor, window, cx)
23899        })
23900        .unwrap()
23901        .await
23902        .unwrap();
23903    assert_eq!(
23904        actions, actions_after_edits,
23905        "For the same selection, same code lens actions should be returned"
23906    );
23907
23908    let _responses =
23909        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23910            panic!("No more code lens requests are expected");
23911        });
23912    editor.update_in(cx, |editor, window, cx| {
23913        editor.select_all(&SelectAll, window, cx);
23914    });
23915    cx.executor().run_until_parked();
23916    let new_actions = cx
23917        .update_window(*workspace, |_, window, cx| {
23918            project.code_actions(&buffer, anchor..anchor, window, cx)
23919        })
23920        .unwrap()
23921        .await
23922        .unwrap();
23923    assert_eq!(
23924        actions, new_actions,
23925        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
23926    );
23927}
23928
23929#[gpui::test]
23930async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
23931    init_test(cx, |_| {});
23932
23933    let fs = FakeFs::new(cx.executor());
23934    let main_text = r#"fn main() {
23935println!("1");
23936println!("2");
23937println!("3");
23938println!("4");
23939println!("5");
23940}"#;
23941    let lib_text = "mod foo {}";
23942    fs.insert_tree(
23943        path!("/a"),
23944        json!({
23945            "lib.rs": lib_text,
23946            "main.rs": main_text,
23947        }),
23948    )
23949    .await;
23950
23951    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23952    let (workspace, cx) =
23953        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23954    let worktree_id = workspace.update(cx, |workspace, cx| {
23955        workspace.project().update(cx, |project, cx| {
23956            project.worktrees(cx).next().unwrap().read(cx).id()
23957        })
23958    });
23959
23960    let expected_ranges = vec![
23961        Point::new(0, 0)..Point::new(0, 0),
23962        Point::new(1, 0)..Point::new(1, 1),
23963        Point::new(2, 0)..Point::new(2, 2),
23964        Point::new(3, 0)..Point::new(3, 3),
23965    ];
23966
23967    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23968    let editor_1 = workspace
23969        .update_in(cx, |workspace, window, cx| {
23970            workspace.open_path(
23971                (worktree_id, rel_path("main.rs")),
23972                Some(pane_1.downgrade()),
23973                true,
23974                window,
23975                cx,
23976            )
23977        })
23978        .unwrap()
23979        .await
23980        .downcast::<Editor>()
23981        .unwrap();
23982    pane_1.update(cx, |pane, cx| {
23983        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23984        open_editor.update(cx, |editor, cx| {
23985            assert_eq!(
23986                editor.display_text(cx),
23987                main_text,
23988                "Original main.rs text on initial open",
23989            );
23990            assert_eq!(
23991                editor
23992                    .selections
23993                    .all::<Point>(&editor.display_snapshot(cx))
23994                    .into_iter()
23995                    .map(|s| s.range())
23996                    .collect::<Vec<_>>(),
23997                vec![Point::zero()..Point::zero()],
23998                "Default selections on initial open",
23999            );
24000        })
24001    });
24002    editor_1.update_in(cx, |editor, window, cx| {
24003        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24004            s.select_ranges(expected_ranges.clone());
24005        });
24006    });
24007
24008    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
24009        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
24010    });
24011    let editor_2 = workspace
24012        .update_in(cx, |workspace, window, cx| {
24013            workspace.open_path(
24014                (worktree_id, rel_path("main.rs")),
24015                Some(pane_2.downgrade()),
24016                true,
24017                window,
24018                cx,
24019            )
24020        })
24021        .unwrap()
24022        .await
24023        .downcast::<Editor>()
24024        .unwrap();
24025    pane_2.update(cx, |pane, cx| {
24026        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24027        open_editor.update(cx, |editor, cx| {
24028            assert_eq!(
24029                editor.display_text(cx),
24030                main_text,
24031                "Original main.rs text on initial open in another panel",
24032            );
24033            assert_eq!(
24034                editor
24035                    .selections
24036                    .all::<Point>(&editor.display_snapshot(cx))
24037                    .into_iter()
24038                    .map(|s| s.range())
24039                    .collect::<Vec<_>>(),
24040                vec![Point::zero()..Point::zero()],
24041                "Default selections on initial open in another panel",
24042            );
24043        })
24044    });
24045
24046    editor_2.update_in(cx, |editor, window, cx| {
24047        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
24048    });
24049
24050    let _other_editor_1 = workspace
24051        .update_in(cx, |workspace, window, cx| {
24052            workspace.open_path(
24053                (worktree_id, rel_path("lib.rs")),
24054                Some(pane_1.downgrade()),
24055                true,
24056                window,
24057                cx,
24058            )
24059        })
24060        .unwrap()
24061        .await
24062        .downcast::<Editor>()
24063        .unwrap();
24064    pane_1
24065        .update_in(cx, |pane, window, cx| {
24066            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
24067        })
24068        .await
24069        .unwrap();
24070    drop(editor_1);
24071    pane_1.update(cx, |pane, cx| {
24072        pane.active_item()
24073            .unwrap()
24074            .downcast::<Editor>()
24075            .unwrap()
24076            .update(cx, |editor, cx| {
24077                assert_eq!(
24078                    editor.display_text(cx),
24079                    lib_text,
24080                    "Other file should be open and active",
24081                );
24082            });
24083        assert_eq!(pane.items().count(), 1, "No other editors should be open");
24084    });
24085
24086    let _other_editor_2 = workspace
24087        .update_in(cx, |workspace, window, cx| {
24088            workspace.open_path(
24089                (worktree_id, rel_path("lib.rs")),
24090                Some(pane_2.downgrade()),
24091                true,
24092                window,
24093                cx,
24094            )
24095        })
24096        .unwrap()
24097        .await
24098        .downcast::<Editor>()
24099        .unwrap();
24100    pane_2
24101        .update_in(cx, |pane, window, cx| {
24102            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
24103        })
24104        .await
24105        .unwrap();
24106    drop(editor_2);
24107    pane_2.update(cx, |pane, cx| {
24108        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24109        open_editor.update(cx, |editor, cx| {
24110            assert_eq!(
24111                editor.display_text(cx),
24112                lib_text,
24113                "Other file should be open and active in another panel too",
24114            );
24115        });
24116        assert_eq!(
24117            pane.items().count(),
24118            1,
24119            "No other editors should be open in another pane",
24120        );
24121    });
24122
24123    let _editor_1_reopened = workspace
24124        .update_in(cx, |workspace, window, cx| {
24125            workspace.open_path(
24126                (worktree_id, rel_path("main.rs")),
24127                Some(pane_1.downgrade()),
24128                true,
24129                window,
24130                cx,
24131            )
24132        })
24133        .unwrap()
24134        .await
24135        .downcast::<Editor>()
24136        .unwrap();
24137    let _editor_2_reopened = workspace
24138        .update_in(cx, |workspace, window, cx| {
24139            workspace.open_path(
24140                (worktree_id, rel_path("main.rs")),
24141                Some(pane_2.downgrade()),
24142                true,
24143                window,
24144                cx,
24145            )
24146        })
24147        .unwrap()
24148        .await
24149        .downcast::<Editor>()
24150        .unwrap();
24151    pane_1.update(cx, |pane, cx| {
24152        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24153        open_editor.update(cx, |editor, cx| {
24154            assert_eq!(
24155                editor.display_text(cx),
24156                main_text,
24157                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
24158            );
24159            assert_eq!(
24160                editor
24161                    .selections
24162                    .all::<Point>(&editor.display_snapshot(cx))
24163                    .into_iter()
24164                    .map(|s| s.range())
24165                    .collect::<Vec<_>>(),
24166                expected_ranges,
24167                "Previous editor in the 1st panel had selections and should get them restored on reopen",
24168            );
24169        })
24170    });
24171    pane_2.update(cx, |pane, cx| {
24172        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24173        open_editor.update(cx, |editor, cx| {
24174            assert_eq!(
24175                editor.display_text(cx),
24176                r#"fn main() {
24177⋯rintln!("1");
24178⋯intln!("2");
24179⋯ntln!("3");
24180println!("4");
24181println!("5");
24182}"#,
24183                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
24184            );
24185            assert_eq!(
24186                editor
24187                    .selections
24188                    .all::<Point>(&editor.display_snapshot(cx))
24189                    .into_iter()
24190                    .map(|s| s.range())
24191                    .collect::<Vec<_>>(),
24192                vec![Point::zero()..Point::zero()],
24193                "Previous editor in the 2nd pane had no selections changed hence should restore none",
24194            );
24195        })
24196    });
24197}
24198
24199#[gpui::test]
24200async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
24201    init_test(cx, |_| {});
24202
24203    let fs = FakeFs::new(cx.executor());
24204    let main_text = r#"fn main() {
24205println!("1");
24206println!("2");
24207println!("3");
24208println!("4");
24209println!("5");
24210}"#;
24211    let lib_text = "mod foo {}";
24212    fs.insert_tree(
24213        path!("/a"),
24214        json!({
24215            "lib.rs": lib_text,
24216            "main.rs": main_text,
24217        }),
24218    )
24219    .await;
24220
24221    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24222    let (workspace, cx) =
24223        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24224    let worktree_id = workspace.update(cx, |workspace, cx| {
24225        workspace.project().update(cx, |project, cx| {
24226            project.worktrees(cx).next().unwrap().read(cx).id()
24227        })
24228    });
24229
24230    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24231    let editor = workspace
24232        .update_in(cx, |workspace, window, cx| {
24233            workspace.open_path(
24234                (worktree_id, rel_path("main.rs")),
24235                Some(pane.downgrade()),
24236                true,
24237                window,
24238                cx,
24239            )
24240        })
24241        .unwrap()
24242        .await
24243        .downcast::<Editor>()
24244        .unwrap();
24245    pane.update(cx, |pane, cx| {
24246        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24247        open_editor.update(cx, |editor, cx| {
24248            assert_eq!(
24249                editor.display_text(cx),
24250                main_text,
24251                "Original main.rs text on initial open",
24252            );
24253        })
24254    });
24255    editor.update_in(cx, |editor, window, cx| {
24256        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
24257    });
24258
24259    cx.update_global(|store: &mut SettingsStore, cx| {
24260        store.update_user_settings(cx, |s| {
24261            s.workspace.restore_on_file_reopen = Some(false);
24262        });
24263    });
24264    editor.update_in(cx, |editor, window, cx| {
24265        editor.fold_ranges(
24266            vec![
24267                Point::new(1, 0)..Point::new(1, 1),
24268                Point::new(2, 0)..Point::new(2, 2),
24269                Point::new(3, 0)..Point::new(3, 3),
24270            ],
24271            false,
24272            window,
24273            cx,
24274        );
24275    });
24276    pane.update_in(cx, |pane, window, cx| {
24277        pane.close_all_items(&CloseAllItems::default(), window, cx)
24278    })
24279    .await
24280    .unwrap();
24281    pane.update(cx, |pane, _| {
24282        assert!(pane.active_item().is_none());
24283    });
24284    cx.update_global(|store: &mut SettingsStore, cx| {
24285        store.update_user_settings(cx, |s| {
24286            s.workspace.restore_on_file_reopen = Some(true);
24287        });
24288    });
24289
24290    let _editor_reopened = workspace
24291        .update_in(cx, |workspace, window, cx| {
24292            workspace.open_path(
24293                (worktree_id, rel_path("main.rs")),
24294                Some(pane.downgrade()),
24295                true,
24296                window,
24297                cx,
24298            )
24299        })
24300        .unwrap()
24301        .await
24302        .downcast::<Editor>()
24303        .unwrap();
24304    pane.update(cx, |pane, cx| {
24305        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24306        open_editor.update(cx, |editor, cx| {
24307            assert_eq!(
24308                editor.display_text(cx),
24309                main_text,
24310                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
24311            );
24312        })
24313    });
24314}
24315
24316#[gpui::test]
24317async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
24318    struct EmptyModalView {
24319        focus_handle: gpui::FocusHandle,
24320    }
24321    impl EventEmitter<DismissEvent> for EmptyModalView {}
24322    impl Render for EmptyModalView {
24323        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
24324            div()
24325        }
24326    }
24327    impl Focusable for EmptyModalView {
24328        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
24329            self.focus_handle.clone()
24330        }
24331    }
24332    impl workspace::ModalView for EmptyModalView {}
24333    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
24334        EmptyModalView {
24335            focus_handle: cx.focus_handle(),
24336        }
24337    }
24338
24339    init_test(cx, |_| {});
24340
24341    let fs = FakeFs::new(cx.executor());
24342    let project = Project::test(fs, [], cx).await;
24343    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24344    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
24345    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24346    let editor = cx.new_window_entity(|window, cx| {
24347        Editor::new(
24348            EditorMode::full(),
24349            buffer,
24350            Some(project.clone()),
24351            window,
24352            cx,
24353        )
24354    });
24355    workspace
24356        .update(cx, |workspace, window, cx| {
24357            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
24358        })
24359        .unwrap();
24360    editor.update_in(cx, |editor, window, cx| {
24361        editor.open_context_menu(&OpenContextMenu, window, cx);
24362        assert!(editor.mouse_context_menu.is_some());
24363    });
24364    workspace
24365        .update(cx, |workspace, window, cx| {
24366            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
24367        })
24368        .unwrap();
24369    cx.read(|cx| {
24370        assert!(editor.read(cx).mouse_context_menu.is_none());
24371    });
24372}
24373
24374fn set_linked_edit_ranges(
24375    opening: (Point, Point),
24376    closing: (Point, Point),
24377    editor: &mut Editor,
24378    cx: &mut Context<Editor>,
24379) {
24380    let Some((buffer, _)) = editor
24381        .buffer
24382        .read(cx)
24383        .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
24384    else {
24385        panic!("Failed to get buffer for selection position");
24386    };
24387    let buffer = buffer.read(cx);
24388    let buffer_id = buffer.remote_id();
24389    let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
24390    let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
24391    let mut linked_ranges = HashMap::default();
24392    linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
24393    editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
24394}
24395
24396#[gpui::test]
24397async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
24398    init_test(cx, |_| {});
24399
24400    let fs = FakeFs::new(cx.executor());
24401    fs.insert_file(path!("/file.html"), Default::default())
24402        .await;
24403
24404    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
24405
24406    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24407    let html_language = Arc::new(Language::new(
24408        LanguageConfig {
24409            name: "HTML".into(),
24410            matcher: LanguageMatcher {
24411                path_suffixes: vec!["html".to_string()],
24412                ..LanguageMatcher::default()
24413            },
24414            brackets: BracketPairConfig {
24415                pairs: vec![BracketPair {
24416                    start: "<".into(),
24417                    end: ">".into(),
24418                    close: true,
24419                    ..Default::default()
24420                }],
24421                ..Default::default()
24422            },
24423            ..Default::default()
24424        },
24425        Some(tree_sitter_html::LANGUAGE.into()),
24426    ));
24427    language_registry.add(html_language);
24428    let mut fake_servers = language_registry.register_fake_lsp(
24429        "HTML",
24430        FakeLspAdapter {
24431            capabilities: lsp::ServerCapabilities {
24432                completion_provider: Some(lsp::CompletionOptions {
24433                    resolve_provider: Some(true),
24434                    ..Default::default()
24435                }),
24436                ..Default::default()
24437            },
24438            ..Default::default()
24439        },
24440    );
24441
24442    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24443    let cx = &mut VisualTestContext::from_window(*workspace, cx);
24444
24445    let worktree_id = workspace
24446        .update(cx, |workspace, _window, cx| {
24447            workspace.project().update(cx, |project, cx| {
24448                project.worktrees(cx).next().unwrap().read(cx).id()
24449            })
24450        })
24451        .unwrap();
24452    project
24453        .update(cx, |project, cx| {
24454            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
24455        })
24456        .await
24457        .unwrap();
24458    let editor = workspace
24459        .update(cx, |workspace, window, cx| {
24460            workspace.open_path((worktree_id, rel_path("file.html")), None, true, window, cx)
24461        })
24462        .unwrap()
24463        .await
24464        .unwrap()
24465        .downcast::<Editor>()
24466        .unwrap();
24467
24468    let fake_server = fake_servers.next().await.unwrap();
24469    editor.update_in(cx, |editor, window, cx| {
24470        editor.set_text("<ad></ad>", window, cx);
24471        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24472            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
24473        });
24474        set_linked_edit_ranges(
24475            (Point::new(0, 1), Point::new(0, 3)),
24476            (Point::new(0, 6), Point::new(0, 8)),
24477            editor,
24478            cx,
24479        );
24480    });
24481    let mut completion_handle =
24482        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
24483            Ok(Some(lsp::CompletionResponse::Array(vec![
24484                lsp::CompletionItem {
24485                    label: "head".to_string(),
24486                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
24487                        lsp::InsertReplaceEdit {
24488                            new_text: "head".to_string(),
24489                            insert: lsp::Range::new(
24490                                lsp::Position::new(0, 1),
24491                                lsp::Position::new(0, 3),
24492                            ),
24493                            replace: lsp::Range::new(
24494                                lsp::Position::new(0, 1),
24495                                lsp::Position::new(0, 3),
24496                            ),
24497                        },
24498                    )),
24499                    ..Default::default()
24500                },
24501            ])))
24502        });
24503    editor.update_in(cx, |editor, window, cx| {
24504        editor.show_completions(&ShowCompletions, window, cx);
24505    });
24506    cx.run_until_parked();
24507    completion_handle.next().await.unwrap();
24508    editor.update(cx, |editor, _| {
24509        assert!(
24510            editor.context_menu_visible(),
24511            "Completion menu should be visible"
24512        );
24513    });
24514    editor.update_in(cx, |editor, window, cx| {
24515        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
24516    });
24517    cx.executor().run_until_parked();
24518    editor.update(cx, |editor, cx| {
24519        assert_eq!(editor.text(cx), "<head></head>");
24520    });
24521}
24522
24523#[gpui::test]
24524async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
24525    init_test(cx, |_| {});
24526
24527    let mut cx = EditorTestContext::new(cx).await;
24528    let language = Arc::new(Language::new(
24529        LanguageConfig {
24530            name: "TSX".into(),
24531            matcher: LanguageMatcher {
24532                path_suffixes: vec!["tsx".to_string()],
24533                ..LanguageMatcher::default()
24534            },
24535            brackets: BracketPairConfig {
24536                pairs: vec![BracketPair {
24537                    start: "<".into(),
24538                    end: ">".into(),
24539                    close: true,
24540                    ..Default::default()
24541                }],
24542                ..Default::default()
24543            },
24544            linked_edit_characters: HashSet::from_iter(['.']),
24545            ..Default::default()
24546        },
24547        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
24548    ));
24549    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24550
24551    // Test typing > does not extend linked pair
24552    cx.set_state("<divˇ<div></div>");
24553    cx.update_editor(|editor, _, cx| {
24554        set_linked_edit_ranges(
24555            (Point::new(0, 1), Point::new(0, 4)),
24556            (Point::new(0, 11), Point::new(0, 14)),
24557            editor,
24558            cx,
24559        );
24560    });
24561    cx.update_editor(|editor, window, cx| {
24562        editor.handle_input(">", window, cx);
24563    });
24564    cx.assert_editor_state("<div>ˇ<div></div>");
24565
24566    // Test typing . do extend linked pair
24567    cx.set_state("<Animatedˇ></Animated>");
24568    cx.update_editor(|editor, _, cx| {
24569        set_linked_edit_ranges(
24570            (Point::new(0, 1), Point::new(0, 9)),
24571            (Point::new(0, 12), Point::new(0, 20)),
24572            editor,
24573            cx,
24574        );
24575    });
24576    cx.update_editor(|editor, window, cx| {
24577        editor.handle_input(".", window, cx);
24578    });
24579    cx.assert_editor_state("<Animated.ˇ></Animated.>");
24580    cx.update_editor(|editor, _, cx| {
24581        set_linked_edit_ranges(
24582            (Point::new(0, 1), Point::new(0, 10)),
24583            (Point::new(0, 13), Point::new(0, 21)),
24584            editor,
24585            cx,
24586        );
24587    });
24588    cx.update_editor(|editor, window, cx| {
24589        editor.handle_input("V", window, cx);
24590    });
24591    cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
24592}
24593
24594#[gpui::test]
24595async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
24596    init_test(cx, |_| {});
24597
24598    let fs = FakeFs::new(cx.executor());
24599    fs.insert_tree(
24600        path!("/root"),
24601        json!({
24602            "a": {
24603                "main.rs": "fn main() {}",
24604            },
24605            "foo": {
24606                "bar": {
24607                    "external_file.rs": "pub mod external {}",
24608                }
24609            }
24610        }),
24611    )
24612    .await;
24613
24614    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
24615    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24616    language_registry.add(rust_lang());
24617    let _fake_servers = language_registry.register_fake_lsp(
24618        "Rust",
24619        FakeLspAdapter {
24620            ..FakeLspAdapter::default()
24621        },
24622    );
24623    let (workspace, cx) =
24624        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24625    let worktree_id = workspace.update(cx, |workspace, cx| {
24626        workspace.project().update(cx, |project, cx| {
24627            project.worktrees(cx).next().unwrap().read(cx).id()
24628        })
24629    });
24630
24631    let assert_language_servers_count =
24632        |expected: usize, context: &str, cx: &mut VisualTestContext| {
24633            project.update(cx, |project, cx| {
24634                let current = project
24635                    .lsp_store()
24636                    .read(cx)
24637                    .as_local()
24638                    .unwrap()
24639                    .language_servers
24640                    .len();
24641                assert_eq!(expected, current, "{context}");
24642            });
24643        };
24644
24645    assert_language_servers_count(
24646        0,
24647        "No servers should be running before any file is open",
24648        cx,
24649    );
24650    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24651    let main_editor = workspace
24652        .update_in(cx, |workspace, window, cx| {
24653            workspace.open_path(
24654                (worktree_id, rel_path("main.rs")),
24655                Some(pane.downgrade()),
24656                true,
24657                window,
24658                cx,
24659            )
24660        })
24661        .unwrap()
24662        .await
24663        .downcast::<Editor>()
24664        .unwrap();
24665    pane.update(cx, |pane, cx| {
24666        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24667        open_editor.update(cx, |editor, cx| {
24668            assert_eq!(
24669                editor.display_text(cx),
24670                "fn main() {}",
24671                "Original main.rs text on initial open",
24672            );
24673        });
24674        assert_eq!(open_editor, main_editor);
24675    });
24676    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
24677
24678    let external_editor = workspace
24679        .update_in(cx, |workspace, window, cx| {
24680            workspace.open_abs_path(
24681                PathBuf::from("/root/foo/bar/external_file.rs"),
24682                OpenOptions::default(),
24683                window,
24684                cx,
24685            )
24686        })
24687        .await
24688        .expect("opening external file")
24689        .downcast::<Editor>()
24690        .expect("downcasted external file's open element to editor");
24691    pane.update(cx, |pane, cx| {
24692        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24693        open_editor.update(cx, |editor, cx| {
24694            assert_eq!(
24695                editor.display_text(cx),
24696                "pub mod external {}",
24697                "External file is open now",
24698            );
24699        });
24700        assert_eq!(open_editor, external_editor);
24701    });
24702    assert_language_servers_count(
24703        1,
24704        "Second, external, *.rs file should join the existing server",
24705        cx,
24706    );
24707
24708    pane.update_in(cx, |pane, window, cx| {
24709        pane.close_active_item(&CloseActiveItem::default(), window, cx)
24710    })
24711    .await
24712    .unwrap();
24713    pane.update_in(cx, |pane, window, cx| {
24714        pane.navigate_backward(&Default::default(), window, cx);
24715    });
24716    cx.run_until_parked();
24717    pane.update(cx, |pane, cx| {
24718        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24719        open_editor.update(cx, |editor, cx| {
24720            assert_eq!(
24721                editor.display_text(cx),
24722                "pub mod external {}",
24723                "External file is open now",
24724            );
24725        });
24726    });
24727    assert_language_servers_count(
24728        1,
24729        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
24730        cx,
24731    );
24732
24733    cx.update(|_, cx| {
24734        workspace::reload(cx);
24735    });
24736    assert_language_servers_count(
24737        1,
24738        "After reloading the worktree with local and external files opened, only one project should be started",
24739        cx,
24740    );
24741}
24742
24743#[gpui::test]
24744async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
24745    init_test(cx, |_| {});
24746
24747    let mut cx = EditorTestContext::new(cx).await;
24748    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24749    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24750
24751    // test cursor move to start of each line on tab
24752    // for `if`, `elif`, `else`, `while`, `with` and `for`
24753    cx.set_state(indoc! {"
24754        def main():
24755        ˇ    for item in items:
24756        ˇ        while item.active:
24757        ˇ            if item.value > 10:
24758        ˇ                continue
24759        ˇ            elif item.value < 0:
24760        ˇ                break
24761        ˇ            else:
24762        ˇ                with item.context() as ctx:
24763        ˇ                    yield count
24764        ˇ        else:
24765        ˇ            log('while else')
24766        ˇ    else:
24767        ˇ        log('for else')
24768    "});
24769    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24770    cx.assert_editor_state(indoc! {"
24771        def main():
24772            ˇfor item in items:
24773                ˇwhile item.active:
24774                    ˇif item.value > 10:
24775                        ˇcontinue
24776                    ˇelif item.value < 0:
24777                        ˇbreak
24778                    ˇelse:
24779                        ˇwith item.context() as ctx:
24780                            ˇyield count
24781                ˇelse:
24782                    ˇlog('while else')
24783            ˇelse:
24784                ˇlog('for else')
24785    "});
24786    // test relative indent is preserved when tab
24787    // for `if`, `elif`, `else`, `while`, `with` and `for`
24788    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24789    cx.assert_editor_state(indoc! {"
24790        def main():
24791                ˇfor item in items:
24792                    ˇwhile item.active:
24793                        ˇif item.value > 10:
24794                            ˇcontinue
24795                        ˇelif item.value < 0:
24796                            ˇbreak
24797                        ˇelse:
24798                            ˇwith item.context() as ctx:
24799                                ˇyield count
24800                    ˇelse:
24801                        ˇlog('while else')
24802                ˇelse:
24803                    ˇlog('for else')
24804    "});
24805
24806    // test cursor move to start of each line on tab
24807    // for `try`, `except`, `else`, `finally`, `match` and `def`
24808    cx.set_state(indoc! {"
24809        def main():
24810        ˇ    try:
24811        ˇ        fetch()
24812        ˇ    except ValueError:
24813        ˇ        handle_error()
24814        ˇ    else:
24815        ˇ        match value:
24816        ˇ            case _:
24817        ˇ    finally:
24818        ˇ        def status():
24819        ˇ            return 0
24820    "});
24821    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24822    cx.assert_editor_state(indoc! {"
24823        def main():
24824            ˇtry:
24825                ˇfetch()
24826            ˇexcept ValueError:
24827                ˇhandle_error()
24828            ˇelse:
24829                ˇmatch value:
24830                    ˇcase _:
24831            ˇfinally:
24832                ˇdef status():
24833                    ˇreturn 0
24834    "});
24835    // test relative indent is preserved when tab
24836    // for `try`, `except`, `else`, `finally`, `match` and `def`
24837    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24838    cx.assert_editor_state(indoc! {"
24839        def main():
24840                ˇtry:
24841                    ˇfetch()
24842                ˇexcept ValueError:
24843                    ˇhandle_error()
24844                ˇelse:
24845                    ˇmatch value:
24846                        ˇcase _:
24847                ˇfinally:
24848                    ˇdef status():
24849                        ˇreturn 0
24850    "});
24851}
24852
24853#[gpui::test]
24854async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
24855    init_test(cx, |_| {});
24856
24857    let mut cx = EditorTestContext::new(cx).await;
24858    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24859    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24860
24861    // test `else` auto outdents when typed inside `if` block
24862    cx.set_state(indoc! {"
24863        def main():
24864            if i == 2:
24865                return
24866                ˇ
24867    "});
24868    cx.update_editor(|editor, window, cx| {
24869        editor.handle_input("else:", window, cx);
24870    });
24871    cx.assert_editor_state(indoc! {"
24872        def main():
24873            if i == 2:
24874                return
24875            else:ˇ
24876    "});
24877
24878    // test `except` auto outdents when typed inside `try` block
24879    cx.set_state(indoc! {"
24880        def main():
24881            try:
24882                i = 2
24883                ˇ
24884    "});
24885    cx.update_editor(|editor, window, cx| {
24886        editor.handle_input("except:", window, cx);
24887    });
24888    cx.assert_editor_state(indoc! {"
24889        def main():
24890            try:
24891                i = 2
24892            except:ˇ
24893    "});
24894
24895    // test `else` auto outdents when typed inside `except` block
24896    cx.set_state(indoc! {"
24897        def main():
24898            try:
24899                i = 2
24900            except:
24901                j = 2
24902                ˇ
24903    "});
24904    cx.update_editor(|editor, window, cx| {
24905        editor.handle_input("else:", window, cx);
24906    });
24907    cx.assert_editor_state(indoc! {"
24908        def main():
24909            try:
24910                i = 2
24911            except:
24912                j = 2
24913            else:ˇ
24914    "});
24915
24916    // test `finally` auto outdents when typed inside `else` block
24917    cx.set_state(indoc! {"
24918        def main():
24919            try:
24920                i = 2
24921            except:
24922                j = 2
24923            else:
24924                k = 2
24925                ˇ
24926    "});
24927    cx.update_editor(|editor, window, cx| {
24928        editor.handle_input("finally:", window, cx);
24929    });
24930    cx.assert_editor_state(indoc! {"
24931        def main():
24932            try:
24933                i = 2
24934            except:
24935                j = 2
24936            else:
24937                k = 2
24938            finally:ˇ
24939    "});
24940
24941    // test `else` does not outdents when typed inside `except` block right after for block
24942    cx.set_state(indoc! {"
24943        def main():
24944            try:
24945                i = 2
24946            except:
24947                for i in range(n):
24948                    pass
24949                ˇ
24950    "});
24951    cx.update_editor(|editor, window, cx| {
24952        editor.handle_input("else:", window, cx);
24953    });
24954    cx.assert_editor_state(indoc! {"
24955        def main():
24956            try:
24957                i = 2
24958            except:
24959                for i in range(n):
24960                    pass
24961                else:ˇ
24962    "});
24963
24964    // test `finally` auto outdents when typed inside `else` block right after for block
24965    cx.set_state(indoc! {"
24966        def main():
24967            try:
24968                i = 2
24969            except:
24970                j = 2
24971            else:
24972                for i in range(n):
24973                    pass
24974                ˇ
24975    "});
24976    cx.update_editor(|editor, window, cx| {
24977        editor.handle_input("finally:", window, cx);
24978    });
24979    cx.assert_editor_state(indoc! {"
24980        def main():
24981            try:
24982                i = 2
24983            except:
24984                j = 2
24985            else:
24986                for i in range(n):
24987                    pass
24988            finally:ˇ
24989    "});
24990
24991    // test `except` outdents to inner "try" block
24992    cx.set_state(indoc! {"
24993        def main():
24994            try:
24995                i = 2
24996                if i == 2:
24997                    try:
24998                        i = 3
24999                        ˇ
25000    "});
25001    cx.update_editor(|editor, window, cx| {
25002        editor.handle_input("except:", window, cx);
25003    });
25004    cx.assert_editor_state(indoc! {"
25005        def main():
25006            try:
25007                i = 2
25008                if i == 2:
25009                    try:
25010                        i = 3
25011                    except:ˇ
25012    "});
25013
25014    // test `except` outdents to outer "try" block
25015    cx.set_state(indoc! {"
25016        def main():
25017            try:
25018                i = 2
25019                if i == 2:
25020                    try:
25021                        i = 3
25022                ˇ
25023    "});
25024    cx.update_editor(|editor, window, cx| {
25025        editor.handle_input("except:", window, cx);
25026    });
25027    cx.assert_editor_state(indoc! {"
25028        def main():
25029            try:
25030                i = 2
25031                if i == 2:
25032                    try:
25033                        i = 3
25034            except:ˇ
25035    "});
25036
25037    // test `else` stays at correct indent when typed after `for` block
25038    cx.set_state(indoc! {"
25039        def main():
25040            for i in range(10):
25041                if i == 3:
25042                    break
25043            ˇ
25044    "});
25045    cx.update_editor(|editor, window, cx| {
25046        editor.handle_input("else:", window, cx);
25047    });
25048    cx.assert_editor_state(indoc! {"
25049        def main():
25050            for i in range(10):
25051                if i == 3:
25052                    break
25053            else:ˇ
25054    "});
25055
25056    // test does not outdent on typing after line with square brackets
25057    cx.set_state(indoc! {"
25058        def f() -> list[str]:
25059            ˇ
25060    "});
25061    cx.update_editor(|editor, window, cx| {
25062        editor.handle_input("a", window, cx);
25063    });
25064    cx.assert_editor_state(indoc! {"
25065        def f() -> list[str]:
2506625067    "});
25068
25069    // test does not outdent on typing : after case keyword
25070    cx.set_state(indoc! {"
25071        match 1:
25072            caseˇ
25073    "});
25074    cx.update_editor(|editor, window, cx| {
25075        editor.handle_input(":", window, cx);
25076    });
25077    cx.assert_editor_state(indoc! {"
25078        match 1:
25079            case:ˇ
25080    "});
25081}
25082
25083#[gpui::test]
25084async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
25085    init_test(cx, |_| {});
25086    update_test_language_settings(cx, |settings| {
25087        settings.defaults.extend_comment_on_newline = Some(false);
25088    });
25089    let mut cx = EditorTestContext::new(cx).await;
25090    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
25091    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25092
25093    // test correct indent after newline on comment
25094    cx.set_state(indoc! {"
25095        # COMMENT:ˇ
25096    "});
25097    cx.update_editor(|editor, window, cx| {
25098        editor.newline(&Newline, window, cx);
25099    });
25100    cx.assert_editor_state(indoc! {"
25101        # COMMENT:
25102        ˇ
25103    "});
25104
25105    // test correct indent after newline in brackets
25106    cx.set_state(indoc! {"
25107        {ˇ}
25108    "});
25109    cx.update_editor(|editor, window, cx| {
25110        editor.newline(&Newline, window, cx);
25111    });
25112    cx.run_until_parked();
25113    cx.assert_editor_state(indoc! {"
25114        {
25115            ˇ
25116        }
25117    "});
25118
25119    cx.set_state(indoc! {"
25120        (ˇ)
25121    "});
25122    cx.update_editor(|editor, window, cx| {
25123        editor.newline(&Newline, window, cx);
25124    });
25125    cx.run_until_parked();
25126    cx.assert_editor_state(indoc! {"
25127        (
25128            ˇ
25129        )
25130    "});
25131
25132    // do not indent after empty lists or dictionaries
25133    cx.set_state(indoc! {"
25134        a = []ˇ
25135    "});
25136    cx.update_editor(|editor, window, cx| {
25137        editor.newline(&Newline, window, cx);
25138    });
25139    cx.run_until_parked();
25140    cx.assert_editor_state(indoc! {"
25141        a = []
25142        ˇ
25143    "});
25144}
25145
25146#[gpui::test]
25147async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
25148    init_test(cx, |_| {});
25149
25150    let mut cx = EditorTestContext::new(cx).await;
25151    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25152    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25153
25154    // test cursor move to start of each line on tab
25155    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
25156    cx.set_state(indoc! {"
25157        function main() {
25158        ˇ    for item in $items; do
25159        ˇ        while [ -n \"$item\" ]; do
25160        ˇ            if [ \"$value\" -gt 10 ]; then
25161        ˇ                continue
25162        ˇ            elif [ \"$value\" -lt 0 ]; then
25163        ˇ                break
25164        ˇ            else
25165        ˇ                echo \"$item\"
25166        ˇ            fi
25167        ˇ        done
25168        ˇ    done
25169        ˇ}
25170    "});
25171    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25172    cx.assert_editor_state(indoc! {"
25173        function main() {
25174            ˇfor item in $items; do
25175                ˇwhile [ -n \"$item\" ]; do
25176                    ˇif [ \"$value\" -gt 10 ]; then
25177                        ˇcontinue
25178                    ˇelif [ \"$value\" -lt 0 ]; then
25179                        ˇbreak
25180                    ˇelse
25181                        ˇecho \"$item\"
25182                    ˇfi
25183                ˇdone
25184            ˇdone
25185        ˇ}
25186    "});
25187    // test relative indent is preserved when tab
25188    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25189    cx.assert_editor_state(indoc! {"
25190        function main() {
25191                ˇfor item in $items; do
25192                    ˇwhile [ -n \"$item\" ]; do
25193                        ˇif [ \"$value\" -gt 10 ]; then
25194                            ˇcontinue
25195                        ˇelif [ \"$value\" -lt 0 ]; then
25196                            ˇbreak
25197                        ˇelse
25198                            ˇecho \"$item\"
25199                        ˇfi
25200                    ˇdone
25201                ˇdone
25202            ˇ}
25203    "});
25204
25205    // test cursor move to start of each line on tab
25206    // for `case` statement with patterns
25207    cx.set_state(indoc! {"
25208        function handle() {
25209        ˇ    case \"$1\" in
25210        ˇ        start)
25211        ˇ            echo \"a\"
25212        ˇ            ;;
25213        ˇ        stop)
25214        ˇ            echo \"b\"
25215        ˇ            ;;
25216        ˇ        *)
25217        ˇ            echo \"c\"
25218        ˇ            ;;
25219        ˇ    esac
25220        ˇ}
25221    "});
25222    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25223    cx.assert_editor_state(indoc! {"
25224        function handle() {
25225            ˇcase \"$1\" in
25226                ˇstart)
25227                    ˇecho \"a\"
25228                    ˇ;;
25229                ˇstop)
25230                    ˇecho \"b\"
25231                    ˇ;;
25232                ˇ*)
25233                    ˇecho \"c\"
25234                    ˇ;;
25235            ˇesac
25236        ˇ}
25237    "});
25238}
25239
25240#[gpui::test]
25241async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
25242    init_test(cx, |_| {});
25243
25244    let mut cx = EditorTestContext::new(cx).await;
25245    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25246    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25247
25248    // test indents on comment insert
25249    cx.set_state(indoc! {"
25250        function main() {
25251        ˇ    for item in $items; do
25252        ˇ        while [ -n \"$item\" ]; do
25253        ˇ            if [ \"$value\" -gt 10 ]; then
25254        ˇ                continue
25255        ˇ            elif [ \"$value\" -lt 0 ]; then
25256        ˇ                break
25257        ˇ            else
25258        ˇ                echo \"$item\"
25259        ˇ            fi
25260        ˇ        done
25261        ˇ    done
25262        ˇ}
25263    "});
25264    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
25265    cx.assert_editor_state(indoc! {"
25266        function main() {
25267        #ˇ    for item in $items; do
25268        #ˇ        while [ -n \"$item\" ]; do
25269        #ˇ            if [ \"$value\" -gt 10 ]; then
25270        #ˇ                continue
25271        #ˇ            elif [ \"$value\" -lt 0 ]; then
25272        #ˇ                break
25273        #ˇ            else
25274        #ˇ                echo \"$item\"
25275        #ˇ            fi
25276        #ˇ        done
25277        #ˇ    done
25278        #ˇ}
25279    "});
25280}
25281
25282#[gpui::test]
25283async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
25284    init_test(cx, |_| {});
25285
25286    let mut cx = EditorTestContext::new(cx).await;
25287    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25288    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25289
25290    // test `else` auto outdents when typed inside `if` block
25291    cx.set_state(indoc! {"
25292        if [ \"$1\" = \"test\" ]; then
25293            echo \"foo bar\"
25294            ˇ
25295    "});
25296    cx.update_editor(|editor, window, cx| {
25297        editor.handle_input("else", window, cx);
25298    });
25299    cx.assert_editor_state(indoc! {"
25300        if [ \"$1\" = \"test\" ]; then
25301            echo \"foo bar\"
25302        elseˇ
25303    "});
25304
25305    // test `elif` auto outdents when typed inside `if` block
25306    cx.set_state(indoc! {"
25307        if [ \"$1\" = \"test\" ]; then
25308            echo \"foo bar\"
25309            ˇ
25310    "});
25311    cx.update_editor(|editor, window, cx| {
25312        editor.handle_input("elif", window, cx);
25313    });
25314    cx.assert_editor_state(indoc! {"
25315        if [ \"$1\" = \"test\" ]; then
25316            echo \"foo bar\"
25317        elifˇ
25318    "});
25319
25320    // test `fi` auto outdents when typed inside `else` block
25321    cx.set_state(indoc! {"
25322        if [ \"$1\" = \"test\" ]; then
25323            echo \"foo bar\"
25324        else
25325            echo \"bar baz\"
25326            ˇ
25327    "});
25328    cx.update_editor(|editor, window, cx| {
25329        editor.handle_input("fi", window, cx);
25330    });
25331    cx.assert_editor_state(indoc! {"
25332        if [ \"$1\" = \"test\" ]; then
25333            echo \"foo bar\"
25334        else
25335            echo \"bar baz\"
25336        fiˇ
25337    "});
25338
25339    // test `done` auto outdents when typed inside `while` block
25340    cx.set_state(indoc! {"
25341        while read line; do
25342            echo \"$line\"
25343            ˇ
25344    "});
25345    cx.update_editor(|editor, window, cx| {
25346        editor.handle_input("done", window, cx);
25347    });
25348    cx.assert_editor_state(indoc! {"
25349        while read line; do
25350            echo \"$line\"
25351        doneˇ
25352    "});
25353
25354    // test `done` auto outdents when typed inside `for` block
25355    cx.set_state(indoc! {"
25356        for file in *.txt; do
25357            cat \"$file\"
25358            ˇ
25359    "});
25360    cx.update_editor(|editor, window, cx| {
25361        editor.handle_input("done", window, cx);
25362    });
25363    cx.assert_editor_state(indoc! {"
25364        for file in *.txt; do
25365            cat \"$file\"
25366        doneˇ
25367    "});
25368
25369    // test `esac` auto outdents when typed inside `case` block
25370    cx.set_state(indoc! {"
25371        case \"$1\" in
25372            start)
25373                echo \"foo bar\"
25374                ;;
25375            stop)
25376                echo \"bar baz\"
25377                ;;
25378            ˇ
25379    "});
25380    cx.update_editor(|editor, window, cx| {
25381        editor.handle_input("esac", window, cx);
25382    });
25383    cx.assert_editor_state(indoc! {"
25384        case \"$1\" in
25385            start)
25386                echo \"foo bar\"
25387                ;;
25388            stop)
25389                echo \"bar baz\"
25390                ;;
25391        esacˇ
25392    "});
25393
25394    // test `*)` auto outdents when typed inside `case` block
25395    cx.set_state(indoc! {"
25396        case \"$1\" in
25397            start)
25398                echo \"foo bar\"
25399                ;;
25400                ˇ
25401    "});
25402    cx.update_editor(|editor, window, cx| {
25403        editor.handle_input("*)", window, cx);
25404    });
25405    cx.assert_editor_state(indoc! {"
25406        case \"$1\" in
25407            start)
25408                echo \"foo bar\"
25409                ;;
25410            *)ˇ
25411    "});
25412
25413    // test `fi` outdents to correct level with nested if blocks
25414    cx.set_state(indoc! {"
25415        if [ \"$1\" = \"test\" ]; then
25416            echo \"outer if\"
25417            if [ \"$2\" = \"debug\" ]; then
25418                echo \"inner if\"
25419                ˇ
25420    "});
25421    cx.update_editor(|editor, window, cx| {
25422        editor.handle_input("fi", window, cx);
25423    });
25424    cx.assert_editor_state(indoc! {"
25425        if [ \"$1\" = \"test\" ]; then
25426            echo \"outer if\"
25427            if [ \"$2\" = \"debug\" ]; then
25428                echo \"inner if\"
25429            fiˇ
25430    "});
25431}
25432
25433#[gpui::test]
25434async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
25435    init_test(cx, |_| {});
25436    update_test_language_settings(cx, |settings| {
25437        settings.defaults.extend_comment_on_newline = Some(false);
25438    });
25439    let mut cx = EditorTestContext::new(cx).await;
25440    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25441    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25442
25443    // test correct indent after newline on comment
25444    cx.set_state(indoc! {"
25445        # COMMENT:ˇ
25446    "});
25447    cx.update_editor(|editor, window, cx| {
25448        editor.newline(&Newline, window, cx);
25449    });
25450    cx.assert_editor_state(indoc! {"
25451        # COMMENT:
25452        ˇ
25453    "});
25454
25455    // test correct indent after newline after `then`
25456    cx.set_state(indoc! {"
25457
25458        if [ \"$1\" = \"test\" ]; thenˇ
25459    "});
25460    cx.update_editor(|editor, window, cx| {
25461        editor.newline(&Newline, window, cx);
25462    });
25463    cx.run_until_parked();
25464    cx.assert_editor_state(indoc! {"
25465
25466        if [ \"$1\" = \"test\" ]; then
25467            ˇ
25468    "});
25469
25470    // test correct indent after newline after `else`
25471    cx.set_state(indoc! {"
25472        if [ \"$1\" = \"test\" ]; then
25473        elseˇ
25474    "});
25475    cx.update_editor(|editor, window, cx| {
25476        editor.newline(&Newline, window, cx);
25477    });
25478    cx.run_until_parked();
25479    cx.assert_editor_state(indoc! {"
25480        if [ \"$1\" = \"test\" ]; then
25481        else
25482            ˇ
25483    "});
25484
25485    // test correct indent after newline after `elif`
25486    cx.set_state(indoc! {"
25487        if [ \"$1\" = \"test\" ]; then
25488        elifˇ
25489    "});
25490    cx.update_editor(|editor, window, cx| {
25491        editor.newline(&Newline, window, cx);
25492    });
25493    cx.run_until_parked();
25494    cx.assert_editor_state(indoc! {"
25495        if [ \"$1\" = \"test\" ]; then
25496        elif
25497            ˇ
25498    "});
25499
25500    // test correct indent after newline after `do`
25501    cx.set_state(indoc! {"
25502        for file in *.txt; doˇ
25503    "});
25504    cx.update_editor(|editor, window, cx| {
25505        editor.newline(&Newline, window, cx);
25506    });
25507    cx.run_until_parked();
25508    cx.assert_editor_state(indoc! {"
25509        for file in *.txt; do
25510            ˇ
25511    "});
25512
25513    // test correct indent after newline after case pattern
25514    cx.set_state(indoc! {"
25515        case \"$1\" in
25516            start)ˇ
25517    "});
25518    cx.update_editor(|editor, window, cx| {
25519        editor.newline(&Newline, window, cx);
25520    });
25521    cx.run_until_parked();
25522    cx.assert_editor_state(indoc! {"
25523        case \"$1\" in
25524            start)
25525                ˇ
25526    "});
25527
25528    // test correct indent after newline after case pattern
25529    cx.set_state(indoc! {"
25530        case \"$1\" in
25531            start)
25532                ;;
25533            *)ˇ
25534    "});
25535    cx.update_editor(|editor, window, cx| {
25536        editor.newline(&Newline, window, cx);
25537    });
25538    cx.run_until_parked();
25539    cx.assert_editor_state(indoc! {"
25540        case \"$1\" in
25541            start)
25542                ;;
25543            *)
25544                ˇ
25545    "});
25546
25547    // test correct indent after newline after function opening brace
25548    cx.set_state(indoc! {"
25549        function test() {ˇ}
25550    "});
25551    cx.update_editor(|editor, window, cx| {
25552        editor.newline(&Newline, window, cx);
25553    });
25554    cx.run_until_parked();
25555    cx.assert_editor_state(indoc! {"
25556        function test() {
25557            ˇ
25558        }
25559    "});
25560
25561    // test no extra indent after semicolon on same line
25562    cx.set_state(indoc! {"
25563        echo \"test\"25564    "});
25565    cx.update_editor(|editor, window, cx| {
25566        editor.newline(&Newline, window, cx);
25567    });
25568    cx.run_until_parked();
25569    cx.assert_editor_state(indoc! {"
25570        echo \"test\";
25571        ˇ
25572    "});
25573}
25574
25575fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
25576    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
25577    point..point
25578}
25579
25580#[track_caller]
25581fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
25582    let (text, ranges) = marked_text_ranges(marked_text, true);
25583    assert_eq!(editor.text(cx), text);
25584    assert_eq!(
25585        editor.selections.ranges(&editor.display_snapshot(cx)),
25586        ranges,
25587        "Assert selections are {}",
25588        marked_text
25589    );
25590}
25591
25592pub fn handle_signature_help_request(
25593    cx: &mut EditorLspTestContext,
25594    mocked_response: lsp::SignatureHelp,
25595) -> impl Future<Output = ()> + use<> {
25596    let mut request =
25597        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
25598            let mocked_response = mocked_response.clone();
25599            async move { Ok(Some(mocked_response)) }
25600        });
25601
25602    async move {
25603        request.next().await;
25604    }
25605}
25606
25607#[track_caller]
25608pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
25609    cx.update_editor(|editor, _, _| {
25610        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
25611            let entries = menu.entries.borrow();
25612            let entries = entries
25613                .iter()
25614                .map(|entry| entry.string.as_str())
25615                .collect::<Vec<_>>();
25616            assert_eq!(entries, expected);
25617        } else {
25618            panic!("Expected completions menu");
25619        }
25620    });
25621}
25622
25623/// Handle completion request passing a marked string specifying where the completion
25624/// should be triggered from using '|' character, what range should be replaced, and what completions
25625/// should be returned using '<' and '>' to delimit the range.
25626///
25627/// Also see `handle_completion_request_with_insert_and_replace`.
25628#[track_caller]
25629pub fn handle_completion_request(
25630    marked_string: &str,
25631    completions: Vec<&'static str>,
25632    is_incomplete: bool,
25633    counter: Arc<AtomicUsize>,
25634    cx: &mut EditorLspTestContext,
25635) -> impl Future<Output = ()> {
25636    let complete_from_marker: TextRangeMarker = '|'.into();
25637    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25638    let (_, mut marked_ranges) = marked_text_ranges_by(
25639        marked_string,
25640        vec![complete_from_marker.clone(), replace_range_marker.clone()],
25641    );
25642
25643    let complete_from_position =
25644        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25645    let replace_range =
25646        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25647
25648    let mut request =
25649        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25650            let completions = completions.clone();
25651            counter.fetch_add(1, atomic::Ordering::Release);
25652            async move {
25653                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25654                assert_eq!(
25655                    params.text_document_position.position,
25656                    complete_from_position
25657                );
25658                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
25659                    is_incomplete,
25660                    item_defaults: None,
25661                    items: completions
25662                        .iter()
25663                        .map(|completion_text| lsp::CompletionItem {
25664                            label: completion_text.to_string(),
25665                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
25666                                range: replace_range,
25667                                new_text: completion_text.to_string(),
25668                            })),
25669                            ..Default::default()
25670                        })
25671                        .collect(),
25672                })))
25673            }
25674        });
25675
25676    async move {
25677        request.next().await;
25678    }
25679}
25680
25681/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
25682/// given instead, which also contains an `insert` range.
25683///
25684/// This function uses markers to define ranges:
25685/// - `|` marks the cursor position
25686/// - `<>` marks the replace range
25687/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
25688pub fn handle_completion_request_with_insert_and_replace(
25689    cx: &mut EditorLspTestContext,
25690    marked_string: &str,
25691    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
25692    counter: Arc<AtomicUsize>,
25693) -> impl Future<Output = ()> {
25694    let complete_from_marker: TextRangeMarker = '|'.into();
25695    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25696    let insert_range_marker: TextRangeMarker = ('{', '}').into();
25697
25698    let (_, mut marked_ranges) = marked_text_ranges_by(
25699        marked_string,
25700        vec![
25701            complete_from_marker.clone(),
25702            replace_range_marker.clone(),
25703            insert_range_marker.clone(),
25704        ],
25705    );
25706
25707    let complete_from_position =
25708        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25709    let replace_range =
25710        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25711
25712    let insert_range = match marked_ranges.remove(&insert_range_marker) {
25713        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
25714        _ => lsp::Range {
25715            start: replace_range.start,
25716            end: complete_from_position,
25717        },
25718    };
25719
25720    let mut request =
25721        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25722            let completions = completions.clone();
25723            counter.fetch_add(1, atomic::Ordering::Release);
25724            async move {
25725                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25726                assert_eq!(
25727                    params.text_document_position.position, complete_from_position,
25728                    "marker `|` position doesn't match",
25729                );
25730                Ok(Some(lsp::CompletionResponse::Array(
25731                    completions
25732                        .iter()
25733                        .map(|(label, new_text)| lsp::CompletionItem {
25734                            label: label.to_string(),
25735                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
25736                                lsp::InsertReplaceEdit {
25737                                    insert: insert_range,
25738                                    replace: replace_range,
25739                                    new_text: new_text.to_string(),
25740                                },
25741                            )),
25742                            ..Default::default()
25743                        })
25744                        .collect(),
25745                )))
25746            }
25747        });
25748
25749    async move {
25750        request.next().await;
25751    }
25752}
25753
25754fn handle_resolve_completion_request(
25755    cx: &mut EditorLspTestContext,
25756    edits: Option<Vec<(&'static str, &'static str)>>,
25757) -> impl Future<Output = ()> {
25758    let edits = edits.map(|edits| {
25759        edits
25760            .iter()
25761            .map(|(marked_string, new_text)| {
25762                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
25763                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
25764                lsp::TextEdit::new(replace_range, new_text.to_string())
25765            })
25766            .collect::<Vec<_>>()
25767    });
25768
25769    let mut request =
25770        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
25771            let edits = edits.clone();
25772            async move {
25773                Ok(lsp::CompletionItem {
25774                    additional_text_edits: edits,
25775                    ..Default::default()
25776                })
25777            }
25778        });
25779
25780    async move {
25781        request.next().await;
25782    }
25783}
25784
25785pub(crate) fn update_test_language_settings(
25786    cx: &mut TestAppContext,
25787    f: impl Fn(&mut AllLanguageSettingsContent),
25788) {
25789    cx.update(|cx| {
25790        SettingsStore::update_global(cx, |store, cx| {
25791            store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
25792        });
25793    });
25794}
25795
25796pub(crate) fn update_test_project_settings(
25797    cx: &mut TestAppContext,
25798    f: impl Fn(&mut ProjectSettingsContent),
25799) {
25800    cx.update(|cx| {
25801        SettingsStore::update_global(cx, |store, cx| {
25802            store.update_user_settings(cx, |settings| f(&mut settings.project));
25803        });
25804    });
25805}
25806
25807pub(crate) fn update_test_editor_settings(
25808    cx: &mut TestAppContext,
25809    f: impl Fn(&mut EditorSettingsContent),
25810) {
25811    cx.update(|cx| {
25812        SettingsStore::update_global(cx, |store, cx| {
25813            store.update_user_settings(cx, |settings| f(&mut settings.editor));
25814        })
25815    })
25816}
25817
25818pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
25819    cx.update(|cx| {
25820        assets::Assets.load_test_fonts(cx);
25821        let store = SettingsStore::test(cx);
25822        cx.set_global(store);
25823        theme::init(theme::LoadThemes::JustBase, cx);
25824        release_channel::init(SemanticVersion::default(), cx);
25825        crate::init(cx);
25826    });
25827    zlog::init_test();
25828    update_test_language_settings(cx, f);
25829}
25830
25831#[track_caller]
25832fn assert_hunk_revert(
25833    not_reverted_text_with_selections: &str,
25834    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
25835    expected_reverted_text_with_selections: &str,
25836    base_text: &str,
25837    cx: &mut EditorLspTestContext,
25838) {
25839    cx.set_state(not_reverted_text_with_selections);
25840    cx.set_head_text(base_text);
25841    cx.executor().run_until_parked();
25842
25843    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
25844        let snapshot = editor.snapshot(window, cx);
25845        let reverted_hunk_statuses = snapshot
25846            .buffer_snapshot()
25847            .diff_hunks_in_range(0..snapshot.buffer_snapshot().len())
25848            .map(|hunk| hunk.status().kind)
25849            .collect::<Vec<_>>();
25850
25851        editor.git_restore(&Default::default(), window, cx);
25852        reverted_hunk_statuses
25853    });
25854    cx.executor().run_until_parked();
25855    cx.assert_editor_state(expected_reverted_text_with_selections);
25856    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
25857}
25858
25859#[gpui::test(iterations = 10)]
25860async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
25861    init_test(cx, |_| {});
25862
25863    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
25864    let counter = diagnostic_requests.clone();
25865
25866    let fs = FakeFs::new(cx.executor());
25867    fs.insert_tree(
25868        path!("/a"),
25869        json!({
25870            "first.rs": "fn main() { let a = 5; }",
25871            "second.rs": "// Test file",
25872        }),
25873    )
25874    .await;
25875
25876    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25877    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25878    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25879
25880    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25881    language_registry.add(rust_lang());
25882    let mut fake_servers = language_registry.register_fake_lsp(
25883        "Rust",
25884        FakeLspAdapter {
25885            capabilities: lsp::ServerCapabilities {
25886                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
25887                    lsp::DiagnosticOptions {
25888                        identifier: None,
25889                        inter_file_dependencies: true,
25890                        workspace_diagnostics: true,
25891                        work_done_progress_options: Default::default(),
25892                    },
25893                )),
25894                ..Default::default()
25895            },
25896            ..Default::default()
25897        },
25898    );
25899
25900    let editor = workspace
25901        .update(cx, |workspace, window, cx| {
25902            workspace.open_abs_path(
25903                PathBuf::from(path!("/a/first.rs")),
25904                OpenOptions::default(),
25905                window,
25906                cx,
25907            )
25908        })
25909        .unwrap()
25910        .await
25911        .unwrap()
25912        .downcast::<Editor>()
25913        .unwrap();
25914    let fake_server = fake_servers.next().await.unwrap();
25915    let server_id = fake_server.server.server_id();
25916    let mut first_request = fake_server
25917        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
25918            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
25919            let result_id = Some(new_result_id.to_string());
25920            assert_eq!(
25921                params.text_document.uri,
25922                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25923            );
25924            async move {
25925                Ok(lsp::DocumentDiagnosticReportResult::Report(
25926                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
25927                        related_documents: None,
25928                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
25929                            items: Vec::new(),
25930                            result_id,
25931                        },
25932                    }),
25933                ))
25934            }
25935        });
25936
25937    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
25938        project.update(cx, |project, cx| {
25939            let buffer_id = editor
25940                .read(cx)
25941                .buffer()
25942                .read(cx)
25943                .as_singleton()
25944                .expect("created a singleton buffer")
25945                .read(cx)
25946                .remote_id();
25947            let buffer_result_id = project
25948                .lsp_store()
25949                .read(cx)
25950                .result_id(server_id, buffer_id, cx);
25951            assert_eq!(expected, buffer_result_id);
25952        });
25953    };
25954
25955    ensure_result_id(None, cx);
25956    cx.executor().advance_clock(Duration::from_millis(60));
25957    cx.executor().run_until_parked();
25958    assert_eq!(
25959        diagnostic_requests.load(atomic::Ordering::Acquire),
25960        1,
25961        "Opening file should trigger diagnostic request"
25962    );
25963    first_request
25964        .next()
25965        .await
25966        .expect("should have sent the first diagnostics pull request");
25967    ensure_result_id(Some("1".to_string()), cx);
25968
25969    // Editing should trigger diagnostics
25970    editor.update_in(cx, |editor, window, cx| {
25971        editor.handle_input("2", window, cx)
25972    });
25973    cx.executor().advance_clock(Duration::from_millis(60));
25974    cx.executor().run_until_parked();
25975    assert_eq!(
25976        diagnostic_requests.load(atomic::Ordering::Acquire),
25977        2,
25978        "Editing should trigger diagnostic request"
25979    );
25980    ensure_result_id(Some("2".to_string()), cx);
25981
25982    // Moving cursor should not trigger diagnostic request
25983    editor.update_in(cx, |editor, window, cx| {
25984        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25985            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
25986        });
25987    });
25988    cx.executor().advance_clock(Duration::from_millis(60));
25989    cx.executor().run_until_parked();
25990    assert_eq!(
25991        diagnostic_requests.load(atomic::Ordering::Acquire),
25992        2,
25993        "Cursor movement should not trigger diagnostic request"
25994    );
25995    ensure_result_id(Some("2".to_string()), cx);
25996    // Multiple rapid edits should be debounced
25997    for _ in 0..5 {
25998        editor.update_in(cx, |editor, window, cx| {
25999            editor.handle_input("x", window, cx)
26000        });
26001    }
26002    cx.executor().advance_clock(Duration::from_millis(60));
26003    cx.executor().run_until_parked();
26004
26005    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
26006    assert!(
26007        final_requests <= 4,
26008        "Multiple rapid edits should be debounced (got {final_requests} requests)",
26009    );
26010    ensure_result_id(Some(final_requests.to_string()), cx);
26011}
26012
26013#[gpui::test]
26014async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
26015    // Regression test for issue #11671
26016    // Previously, adding a cursor after moving multiple cursors would reset
26017    // the cursor count instead of adding to the existing cursors.
26018    init_test(cx, |_| {});
26019    let mut cx = EditorTestContext::new(cx).await;
26020
26021    // Create a simple buffer with cursor at start
26022    cx.set_state(indoc! {"
26023        ˇaaaa
26024        bbbb
26025        cccc
26026        dddd
26027        eeee
26028        ffff
26029        gggg
26030        hhhh"});
26031
26032    // Add 2 cursors below (so we have 3 total)
26033    cx.update_editor(|editor, window, cx| {
26034        editor.add_selection_below(&Default::default(), window, cx);
26035        editor.add_selection_below(&Default::default(), window, cx);
26036    });
26037
26038    // Verify we have 3 cursors
26039    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
26040    assert_eq!(
26041        initial_count, 3,
26042        "Should have 3 cursors after adding 2 below"
26043    );
26044
26045    // Move down one line
26046    cx.update_editor(|editor, window, cx| {
26047        editor.move_down(&MoveDown, window, cx);
26048    });
26049
26050    // Add another cursor below
26051    cx.update_editor(|editor, window, cx| {
26052        editor.add_selection_below(&Default::default(), window, cx);
26053    });
26054
26055    // Should now have 4 cursors (3 original + 1 new)
26056    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
26057    assert_eq!(
26058        final_count, 4,
26059        "Should have 4 cursors after moving and adding another"
26060    );
26061}
26062
26063#[gpui::test]
26064async fn test_add_selection_skip_soft_wrap_option(cx: &mut TestAppContext) {
26065    init_test(cx, |_| {});
26066
26067    let mut cx = EditorTestContext::new(cx).await;
26068
26069    cx.set_state(indoc!(
26070        r#"ˇThis is a very long line that will be wrapped when soft wrapping is enabled
26071           Second line here"#
26072    ));
26073
26074    cx.update_editor(|editor, window, cx| {
26075        // Enable soft wrapping with a narrow width to force soft wrapping and
26076        // confirm that more than 2 rows are being displayed.
26077        editor.set_wrap_width(Some(100.0.into()), cx);
26078        assert!(editor.display_text(cx).lines().count() > 2);
26079
26080        editor.add_selection_below(
26081            &AddSelectionBelow {
26082                skip_soft_wrap: true,
26083            },
26084            window,
26085            cx,
26086        );
26087
26088        assert_eq!(
26089            display_ranges(editor, cx),
26090            &[
26091                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
26092                DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(8), 0),
26093            ]
26094        );
26095
26096        editor.add_selection_above(
26097            &AddSelectionAbove {
26098                skip_soft_wrap: true,
26099            },
26100            window,
26101            cx,
26102        );
26103
26104        assert_eq!(
26105            display_ranges(editor, cx),
26106            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
26107        );
26108
26109        editor.add_selection_below(
26110            &AddSelectionBelow {
26111                skip_soft_wrap: false,
26112            },
26113            window,
26114            cx,
26115        );
26116
26117        assert_eq!(
26118            display_ranges(editor, cx),
26119            &[
26120                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
26121                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
26122            ]
26123        );
26124
26125        editor.add_selection_above(
26126            &AddSelectionAbove {
26127                skip_soft_wrap: false,
26128            },
26129            window,
26130            cx,
26131        );
26132
26133        assert_eq!(
26134            display_ranges(editor, cx),
26135            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
26136        );
26137    });
26138}
26139
26140#[gpui::test(iterations = 10)]
26141async fn test_document_colors(cx: &mut TestAppContext) {
26142    let expected_color = Rgba {
26143        r: 0.33,
26144        g: 0.33,
26145        b: 0.33,
26146        a: 0.33,
26147    };
26148
26149    init_test(cx, |_| {});
26150
26151    let fs = FakeFs::new(cx.executor());
26152    fs.insert_tree(
26153        path!("/a"),
26154        json!({
26155            "first.rs": "fn main() { let a = 5; }",
26156        }),
26157    )
26158    .await;
26159
26160    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
26161    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
26162    let cx = &mut VisualTestContext::from_window(*workspace, cx);
26163
26164    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26165    language_registry.add(rust_lang());
26166    let mut fake_servers = language_registry.register_fake_lsp(
26167        "Rust",
26168        FakeLspAdapter {
26169            capabilities: lsp::ServerCapabilities {
26170                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
26171                ..lsp::ServerCapabilities::default()
26172            },
26173            name: "rust-analyzer",
26174            ..FakeLspAdapter::default()
26175        },
26176    );
26177    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
26178        "Rust",
26179        FakeLspAdapter {
26180            capabilities: lsp::ServerCapabilities {
26181                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
26182                ..lsp::ServerCapabilities::default()
26183            },
26184            name: "not-rust-analyzer",
26185            ..FakeLspAdapter::default()
26186        },
26187    );
26188
26189    let editor = workspace
26190        .update(cx, |workspace, window, cx| {
26191            workspace.open_abs_path(
26192                PathBuf::from(path!("/a/first.rs")),
26193                OpenOptions::default(),
26194                window,
26195                cx,
26196            )
26197        })
26198        .unwrap()
26199        .await
26200        .unwrap()
26201        .downcast::<Editor>()
26202        .unwrap();
26203    let fake_language_server = fake_servers.next().await.unwrap();
26204    let fake_language_server_without_capabilities =
26205        fake_servers_without_capabilities.next().await.unwrap();
26206    let requests_made = Arc::new(AtomicUsize::new(0));
26207    let closure_requests_made = Arc::clone(&requests_made);
26208    let mut color_request_handle = fake_language_server
26209        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
26210            let requests_made = Arc::clone(&closure_requests_made);
26211            async move {
26212                assert_eq!(
26213                    params.text_document.uri,
26214                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
26215                );
26216                requests_made.fetch_add(1, atomic::Ordering::Release);
26217                Ok(vec![
26218                    lsp::ColorInformation {
26219                        range: lsp::Range {
26220                            start: lsp::Position {
26221                                line: 0,
26222                                character: 0,
26223                            },
26224                            end: lsp::Position {
26225                                line: 0,
26226                                character: 1,
26227                            },
26228                        },
26229                        color: lsp::Color {
26230                            red: 0.33,
26231                            green: 0.33,
26232                            blue: 0.33,
26233                            alpha: 0.33,
26234                        },
26235                    },
26236                    lsp::ColorInformation {
26237                        range: lsp::Range {
26238                            start: lsp::Position {
26239                                line: 0,
26240                                character: 0,
26241                            },
26242                            end: lsp::Position {
26243                                line: 0,
26244                                character: 1,
26245                            },
26246                        },
26247                        color: lsp::Color {
26248                            red: 0.33,
26249                            green: 0.33,
26250                            blue: 0.33,
26251                            alpha: 0.33,
26252                        },
26253                    },
26254                ])
26255            }
26256        });
26257
26258    let _handle = fake_language_server_without_capabilities
26259        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
26260            panic!("Should not be called");
26261        });
26262    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26263    color_request_handle.next().await.unwrap();
26264    cx.run_until_parked();
26265    assert_eq!(
26266        1,
26267        requests_made.load(atomic::Ordering::Acquire),
26268        "Should query for colors once per editor open"
26269    );
26270    editor.update_in(cx, |editor, _, cx| {
26271        assert_eq!(
26272            vec![expected_color],
26273            extract_color_inlays(editor, cx),
26274            "Should have an initial inlay"
26275        );
26276    });
26277
26278    // opening another file in a split should not influence the LSP query counter
26279    workspace
26280        .update(cx, |workspace, window, cx| {
26281            assert_eq!(
26282                workspace.panes().len(),
26283                1,
26284                "Should have one pane with one editor"
26285            );
26286            workspace.move_item_to_pane_in_direction(
26287                &MoveItemToPaneInDirection {
26288                    direction: SplitDirection::Right,
26289                    focus: false,
26290                    clone: true,
26291                },
26292                window,
26293                cx,
26294            );
26295        })
26296        .unwrap();
26297    cx.run_until_parked();
26298    workspace
26299        .update(cx, |workspace, _, cx| {
26300            let panes = workspace.panes();
26301            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
26302            for pane in panes {
26303                let editor = pane
26304                    .read(cx)
26305                    .active_item()
26306                    .and_then(|item| item.downcast::<Editor>())
26307                    .expect("Should have opened an editor in each split");
26308                let editor_file = editor
26309                    .read(cx)
26310                    .buffer()
26311                    .read(cx)
26312                    .as_singleton()
26313                    .expect("test deals with singleton buffers")
26314                    .read(cx)
26315                    .file()
26316                    .expect("test buffese should have a file")
26317                    .path();
26318                assert_eq!(
26319                    editor_file.as_ref(),
26320                    rel_path("first.rs"),
26321                    "Both editors should be opened for the same file"
26322                )
26323            }
26324        })
26325        .unwrap();
26326
26327    cx.executor().advance_clock(Duration::from_millis(500));
26328    let save = editor.update_in(cx, |editor, window, cx| {
26329        editor.move_to_end(&MoveToEnd, window, cx);
26330        editor.handle_input("dirty", window, cx);
26331        editor.save(
26332            SaveOptions {
26333                format: true,
26334                autosave: true,
26335            },
26336            project.clone(),
26337            window,
26338            cx,
26339        )
26340    });
26341    save.await.unwrap();
26342
26343    color_request_handle.next().await.unwrap();
26344    cx.run_until_parked();
26345    assert_eq!(
26346        2,
26347        requests_made.load(atomic::Ordering::Acquire),
26348        "Should query for colors once per save (deduplicated) and once per formatting after save"
26349    );
26350
26351    drop(editor);
26352    let close = workspace
26353        .update(cx, |workspace, window, cx| {
26354            workspace.active_pane().update(cx, |pane, cx| {
26355                pane.close_active_item(&CloseActiveItem::default(), window, cx)
26356            })
26357        })
26358        .unwrap();
26359    close.await.unwrap();
26360    let close = workspace
26361        .update(cx, |workspace, window, cx| {
26362            workspace.active_pane().update(cx, |pane, cx| {
26363                pane.close_active_item(&CloseActiveItem::default(), window, cx)
26364            })
26365        })
26366        .unwrap();
26367    close.await.unwrap();
26368    assert_eq!(
26369        2,
26370        requests_made.load(atomic::Ordering::Acquire),
26371        "After saving and closing all editors, no extra requests should be made"
26372    );
26373    workspace
26374        .update(cx, |workspace, _, cx| {
26375            assert!(
26376                workspace.active_item(cx).is_none(),
26377                "Should close all editors"
26378            )
26379        })
26380        .unwrap();
26381
26382    workspace
26383        .update(cx, |workspace, window, cx| {
26384            workspace.active_pane().update(cx, |pane, cx| {
26385                pane.navigate_backward(&workspace::GoBack, window, cx);
26386            })
26387        })
26388        .unwrap();
26389    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26390    cx.run_until_parked();
26391    let editor = workspace
26392        .update(cx, |workspace, _, cx| {
26393            workspace
26394                .active_item(cx)
26395                .expect("Should have reopened the editor again after navigating back")
26396                .downcast::<Editor>()
26397                .expect("Should be an editor")
26398        })
26399        .unwrap();
26400
26401    assert_eq!(
26402        2,
26403        requests_made.load(atomic::Ordering::Acquire),
26404        "Cache should be reused on buffer close and reopen"
26405    );
26406    editor.update(cx, |editor, cx| {
26407        assert_eq!(
26408            vec![expected_color],
26409            extract_color_inlays(editor, cx),
26410            "Should have an initial inlay"
26411        );
26412    });
26413
26414    drop(color_request_handle);
26415    let closure_requests_made = Arc::clone(&requests_made);
26416    let mut empty_color_request_handle = fake_language_server
26417        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
26418            let requests_made = Arc::clone(&closure_requests_made);
26419            async move {
26420                assert_eq!(
26421                    params.text_document.uri,
26422                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
26423                );
26424                requests_made.fetch_add(1, atomic::Ordering::Release);
26425                Ok(Vec::new())
26426            }
26427        });
26428    let save = editor.update_in(cx, |editor, window, cx| {
26429        editor.move_to_end(&MoveToEnd, window, cx);
26430        editor.handle_input("dirty_again", window, cx);
26431        editor.save(
26432            SaveOptions {
26433                format: false,
26434                autosave: true,
26435            },
26436            project.clone(),
26437            window,
26438            cx,
26439        )
26440    });
26441    save.await.unwrap();
26442
26443    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26444    empty_color_request_handle.next().await.unwrap();
26445    cx.run_until_parked();
26446    assert_eq!(
26447        3,
26448        requests_made.load(atomic::Ordering::Acquire),
26449        "Should query for colors once per save only, as formatting was not requested"
26450    );
26451    editor.update(cx, |editor, cx| {
26452        assert_eq!(
26453            Vec::<Rgba>::new(),
26454            extract_color_inlays(editor, cx),
26455            "Should clear all colors when the server returns an empty response"
26456        );
26457    });
26458}
26459
26460#[gpui::test]
26461async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
26462    init_test(cx, |_| {});
26463    let (editor, cx) = cx.add_window_view(Editor::single_line);
26464    editor.update_in(cx, |editor, window, cx| {
26465        editor.set_text("oops\n\nwow\n", window, cx)
26466    });
26467    cx.run_until_parked();
26468    editor.update(cx, |editor, cx| {
26469        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
26470    });
26471    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
26472    cx.run_until_parked();
26473    editor.update(cx, |editor, cx| {
26474        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
26475    });
26476}
26477
26478#[gpui::test]
26479async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
26480    init_test(cx, |_| {});
26481
26482    cx.update(|cx| {
26483        register_project_item::<Editor>(cx);
26484    });
26485
26486    let fs = FakeFs::new(cx.executor());
26487    fs.insert_tree("/root1", json!({})).await;
26488    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
26489        .await;
26490
26491    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
26492    let (workspace, cx) =
26493        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
26494
26495    let worktree_id = project.update(cx, |project, cx| {
26496        project.worktrees(cx).next().unwrap().read(cx).id()
26497    });
26498
26499    let handle = workspace
26500        .update_in(cx, |workspace, window, cx| {
26501            let project_path = (worktree_id, rel_path("one.pdf"));
26502            workspace.open_path(project_path, None, true, window, cx)
26503        })
26504        .await
26505        .unwrap();
26506
26507    assert_eq!(
26508        handle.to_any().entity_type(),
26509        TypeId::of::<InvalidItemView>()
26510    );
26511}
26512
26513#[gpui::test]
26514async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
26515    init_test(cx, |_| {});
26516
26517    let language = Arc::new(Language::new(
26518        LanguageConfig::default(),
26519        Some(tree_sitter_rust::LANGUAGE.into()),
26520    ));
26521
26522    // Test hierarchical sibling navigation
26523    let text = r#"
26524        fn outer() {
26525            if condition {
26526                let a = 1;
26527            }
26528            let b = 2;
26529        }
26530
26531        fn another() {
26532            let c = 3;
26533        }
26534    "#;
26535
26536    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
26537    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
26538    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
26539
26540    // Wait for parsing to complete
26541    editor
26542        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
26543        .await;
26544
26545    editor.update_in(cx, |editor, window, cx| {
26546        // Start by selecting "let a = 1;" inside the if block
26547        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26548            s.select_display_ranges([
26549                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
26550            ]);
26551        });
26552
26553        let initial_selection = editor
26554            .selections
26555            .display_ranges(&editor.display_snapshot(cx));
26556        assert_eq!(initial_selection.len(), 1, "Should have one selection");
26557
26558        // Test select next sibling - should move up levels to find the next sibling
26559        // Since "let a = 1;" has no siblings in the if block, it should move up
26560        // to find "let b = 2;" which is a sibling of the if block
26561        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
26562        let next_selection = editor
26563            .selections
26564            .display_ranges(&editor.display_snapshot(cx));
26565
26566        // Should have a selection and it should be different from the initial
26567        assert_eq!(
26568            next_selection.len(),
26569            1,
26570            "Should have one selection after next"
26571        );
26572        assert_ne!(
26573            next_selection[0], initial_selection[0],
26574            "Next sibling selection should be different"
26575        );
26576
26577        // Test hierarchical navigation by going to the end of the current function
26578        // and trying to navigate to the next function
26579        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26580            s.select_display_ranges([
26581                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
26582            ]);
26583        });
26584
26585        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
26586        let function_next_selection = editor
26587            .selections
26588            .display_ranges(&editor.display_snapshot(cx));
26589
26590        // Should move to the next function
26591        assert_eq!(
26592            function_next_selection.len(),
26593            1,
26594            "Should have one selection after function next"
26595        );
26596
26597        // Test select previous sibling navigation
26598        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
26599        let prev_selection = editor
26600            .selections
26601            .display_ranges(&editor.display_snapshot(cx));
26602
26603        // Should have a selection and it should be different
26604        assert_eq!(
26605            prev_selection.len(),
26606            1,
26607            "Should have one selection after prev"
26608        );
26609        assert_ne!(
26610            prev_selection[0], function_next_selection[0],
26611            "Previous sibling selection should be different from next"
26612        );
26613    });
26614}
26615
26616#[gpui::test]
26617async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
26618    init_test(cx, |_| {});
26619
26620    let mut cx = EditorTestContext::new(cx).await;
26621    cx.set_state(
26622        "let ˇvariable = 42;
26623let another = variable + 1;
26624let result = variable * 2;",
26625    );
26626
26627    // Set up document highlights manually (simulating LSP response)
26628    cx.update_editor(|editor, _window, cx| {
26629        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
26630
26631        // Create highlights for "variable" occurrences
26632        let highlight_ranges = [
26633            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
26634            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
26635            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
26636        ];
26637
26638        let anchor_ranges: Vec<_> = highlight_ranges
26639            .iter()
26640            .map(|range| range.clone().to_anchors(&buffer_snapshot))
26641            .collect();
26642
26643        editor.highlight_background::<DocumentHighlightRead>(
26644            &anchor_ranges,
26645            |theme| theme.colors().editor_document_highlight_read_background,
26646            cx,
26647        );
26648    });
26649
26650    // Go to next highlight - should move to second "variable"
26651    cx.update_editor(|editor, window, cx| {
26652        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26653    });
26654    cx.assert_editor_state(
26655        "let variable = 42;
26656let another = ˇvariable + 1;
26657let result = variable * 2;",
26658    );
26659
26660    // Go to next highlight - should move to third "variable"
26661    cx.update_editor(|editor, window, cx| {
26662        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26663    });
26664    cx.assert_editor_state(
26665        "let variable = 42;
26666let another = variable + 1;
26667let result = ˇvariable * 2;",
26668    );
26669
26670    // Go to next highlight - should stay at third "variable" (no wrap-around)
26671    cx.update_editor(|editor, window, cx| {
26672        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26673    });
26674    cx.assert_editor_state(
26675        "let variable = 42;
26676let another = variable + 1;
26677let result = ˇvariable * 2;",
26678    );
26679
26680    // Now test going backwards from third position
26681    cx.update_editor(|editor, window, cx| {
26682        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26683    });
26684    cx.assert_editor_state(
26685        "let variable = 42;
26686let another = ˇvariable + 1;
26687let result = variable * 2;",
26688    );
26689
26690    // Go to previous highlight - should move to first "variable"
26691    cx.update_editor(|editor, window, cx| {
26692        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26693    });
26694    cx.assert_editor_state(
26695        "let ˇvariable = 42;
26696let another = variable + 1;
26697let result = variable * 2;",
26698    );
26699
26700    // Go to previous highlight - should stay on first "variable"
26701    cx.update_editor(|editor, window, cx| {
26702        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26703    });
26704    cx.assert_editor_state(
26705        "let ˇvariable = 42;
26706let another = variable + 1;
26707let result = variable * 2;",
26708    );
26709}
26710
26711#[gpui::test]
26712async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
26713    cx: &mut gpui::TestAppContext,
26714) {
26715    init_test(cx, |_| {});
26716
26717    let url = "https://zed.dev";
26718
26719    let markdown_language = Arc::new(Language::new(
26720        LanguageConfig {
26721            name: "Markdown".into(),
26722            ..LanguageConfig::default()
26723        },
26724        None,
26725    ));
26726
26727    let mut cx = EditorTestContext::new(cx).await;
26728    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26729    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
26730
26731    cx.update_editor(|editor, window, cx| {
26732        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26733        editor.paste(&Paste, window, cx);
26734    });
26735
26736    cx.assert_editor_state(&format!(
26737        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)"
26738    ));
26739}
26740
26741#[gpui::test]
26742async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
26743    cx: &mut gpui::TestAppContext,
26744) {
26745    init_test(cx, |_| {});
26746
26747    let url = "https://zed.dev";
26748
26749    let markdown_language = Arc::new(Language::new(
26750        LanguageConfig {
26751            name: "Markdown".into(),
26752            ..LanguageConfig::default()
26753        },
26754        None,
26755    ));
26756
26757    let mut cx = EditorTestContext::new(cx).await;
26758    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26759    cx.set_state(&format!(
26760        "Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»"
26761    ));
26762
26763    cx.update_editor(|editor, window, cx| {
26764        editor.copy(&Copy, window, cx);
26765    });
26766
26767    cx.set_state(&format!(
26768        "Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}"
26769    ));
26770
26771    cx.update_editor(|editor, window, cx| {
26772        editor.paste(&Paste, window, cx);
26773    });
26774
26775    cx.assert_editor_state(&format!(
26776        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}"
26777    ));
26778}
26779
26780#[gpui::test]
26781async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link(
26782    cx: &mut gpui::TestAppContext,
26783) {
26784    init_test(cx, |_| {});
26785
26786    let url = "https://zed.dev";
26787
26788    let markdown_language = Arc::new(Language::new(
26789        LanguageConfig {
26790            name: "Markdown".into(),
26791            ..LanguageConfig::default()
26792        },
26793        None,
26794    ));
26795
26796    let mut cx = EditorTestContext::new(cx).await;
26797    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26798    cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»");
26799
26800    cx.update_editor(|editor, window, cx| {
26801        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26802        editor.paste(&Paste, window, cx);
26803    });
26804
26805    cx.assert_editor_state(&format!("Please visit zed's homepage: {url}ˇ"));
26806}
26807
26808#[gpui::test]
26809async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link(
26810    cx: &mut gpui::TestAppContext,
26811) {
26812    init_test(cx, |_| {});
26813
26814    let text = "Awesome";
26815
26816    let markdown_language = Arc::new(Language::new(
26817        LanguageConfig {
26818            name: "Markdown".into(),
26819            ..LanguageConfig::default()
26820        },
26821        None,
26822    ));
26823
26824    let mut cx = EditorTestContext::new(cx).await;
26825    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26826    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»");
26827
26828    cx.update_editor(|editor, window, cx| {
26829        cx.write_to_clipboard(ClipboardItem::new_string(text.to_string()));
26830        editor.paste(&Paste, window, cx);
26831    });
26832
26833    cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ"));
26834}
26835
26836#[gpui::test]
26837async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language(
26838    cx: &mut gpui::TestAppContext,
26839) {
26840    init_test(cx, |_| {});
26841
26842    let url = "https://zed.dev";
26843
26844    let markdown_language = Arc::new(Language::new(
26845        LanguageConfig {
26846            name: "Rust".into(),
26847            ..LanguageConfig::default()
26848        },
26849        None,
26850    ));
26851
26852    let mut cx = EditorTestContext::new(cx).await;
26853    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
26854    cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
26855
26856    cx.update_editor(|editor, window, cx| {
26857        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26858        editor.paste(&Paste, window, cx);
26859    });
26860
26861    cx.assert_editor_state(&format!(
26862        "// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)"
26863    ));
26864}
26865
26866#[gpui::test]
26867async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer(
26868    cx: &mut TestAppContext,
26869) {
26870    init_test(cx, |_| {});
26871
26872    let url = "https://zed.dev";
26873
26874    let markdown_language = Arc::new(Language::new(
26875        LanguageConfig {
26876            name: "Markdown".into(),
26877            ..LanguageConfig::default()
26878        },
26879        None,
26880    ));
26881
26882    let (editor, cx) = cx.add_window_view(|window, cx| {
26883        let multi_buffer = MultiBuffer::build_multi(
26884            [
26885                ("this will embed -> link", vec![Point::row_range(0..1)]),
26886                ("this will replace -> link", vec![Point::row_range(0..1)]),
26887            ],
26888            cx,
26889        );
26890        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
26891        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26892            s.select_ranges(vec![
26893                Point::new(0, 19)..Point::new(0, 23),
26894                Point::new(1, 21)..Point::new(1, 25),
26895            ])
26896        });
26897        let first_buffer_id = multi_buffer
26898            .read(cx)
26899            .excerpt_buffer_ids()
26900            .into_iter()
26901            .next()
26902            .unwrap();
26903        let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
26904        first_buffer.update(cx, |buffer, cx| {
26905            buffer.set_language(Some(markdown_language.clone()), cx);
26906        });
26907
26908        editor
26909    });
26910    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
26911
26912    cx.update_editor(|editor, window, cx| {
26913        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
26914        editor.paste(&Paste, window, cx);
26915    });
26916
26917    cx.assert_editor_state(&format!(
26918        "this will embed -> [link]({url}\nthis will replace -> {url}ˇ"
26919    ));
26920}
26921
26922#[gpui::test]
26923async fn test_race_in_multibuffer_save(cx: &mut TestAppContext) {
26924    init_test(cx, |_| {});
26925
26926    let fs = FakeFs::new(cx.executor());
26927    fs.insert_tree(
26928        path!("/project"),
26929        json!({
26930            "first.rs": "# First Document\nSome content here.",
26931            "second.rs": "Plain text content for second file.",
26932        }),
26933    )
26934    .await;
26935
26936    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
26937    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
26938    let cx = &mut VisualTestContext::from_window(*workspace, cx);
26939
26940    let language = rust_lang();
26941    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26942    language_registry.add(language.clone());
26943    let mut fake_servers = language_registry.register_fake_lsp(
26944        "Rust",
26945        FakeLspAdapter {
26946            ..FakeLspAdapter::default()
26947        },
26948    );
26949
26950    let buffer1 = project
26951        .update(cx, |project, cx| {
26952            project.open_local_buffer(PathBuf::from(path!("/project/first.rs")), cx)
26953        })
26954        .await
26955        .unwrap();
26956    let buffer2 = project
26957        .update(cx, |project, cx| {
26958            project.open_local_buffer(PathBuf::from(path!("/project/second.rs")), cx)
26959        })
26960        .await
26961        .unwrap();
26962
26963    let multi_buffer = cx.new(|cx| {
26964        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
26965        multi_buffer.set_excerpts_for_path(
26966            PathKey::for_buffer(&buffer1, cx),
26967            buffer1.clone(),
26968            [Point::zero()..buffer1.read(cx).max_point()],
26969            3,
26970            cx,
26971        );
26972        multi_buffer.set_excerpts_for_path(
26973            PathKey::for_buffer(&buffer2, cx),
26974            buffer2.clone(),
26975            [Point::zero()..buffer1.read(cx).max_point()],
26976            3,
26977            cx,
26978        );
26979        multi_buffer
26980    });
26981
26982    let (editor, cx) = cx.add_window_view(|window, cx| {
26983        Editor::new(
26984            EditorMode::full(),
26985            multi_buffer,
26986            Some(project.clone()),
26987            window,
26988            cx,
26989        )
26990    });
26991
26992    let fake_language_server = fake_servers.next().await.unwrap();
26993
26994    buffer1.update(cx, |buffer, cx| buffer.edit([(0..0, "hello!")], None, cx));
26995
26996    let save = editor.update_in(cx, |editor, window, cx| {
26997        assert!(editor.is_dirty(cx));
26998
26999        editor.save(
27000            SaveOptions {
27001                format: true,
27002                autosave: true,
27003            },
27004            project,
27005            window,
27006            cx,
27007        )
27008    });
27009    let (start_edit_tx, start_edit_rx) = oneshot::channel();
27010    let (done_edit_tx, done_edit_rx) = oneshot::channel();
27011    let mut done_edit_rx = Some(done_edit_rx);
27012    let mut start_edit_tx = Some(start_edit_tx);
27013
27014    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| {
27015        start_edit_tx.take().unwrap().send(()).unwrap();
27016        let done_edit_rx = done_edit_rx.take().unwrap();
27017        async move {
27018            done_edit_rx.await.unwrap();
27019            Ok(None)
27020        }
27021    });
27022
27023    start_edit_rx.await.unwrap();
27024    buffer2
27025        .update(cx, |buffer, cx| buffer.edit([(0..0, "world!")], None, cx))
27026        .unwrap();
27027
27028    done_edit_tx.send(()).unwrap();
27029
27030    save.await.unwrap();
27031    cx.update(|_, cx| assert!(editor.is_dirty(cx)));
27032}
27033
27034#[track_caller]
27035fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
27036    editor
27037        .all_inlays(cx)
27038        .into_iter()
27039        .filter_map(|inlay| inlay.get_color())
27040        .map(Rgba::from)
27041        .collect()
27042}
27043
27044#[gpui::test]
27045fn test_duplicate_line_up_on_last_line_without_newline(cx: &mut TestAppContext) {
27046    init_test(cx, |_| {});
27047
27048    let editor = cx.add_window(|window, cx| {
27049        let buffer = MultiBuffer::build_simple("line1\nline2", cx);
27050        build_editor(buffer, window, cx)
27051    });
27052
27053    editor
27054        .update(cx, |editor, window, cx| {
27055            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27056                s.select_display_ranges([
27057                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
27058                ])
27059            });
27060
27061            editor.duplicate_line_up(&DuplicateLineUp, window, cx);
27062
27063            assert_eq!(
27064                editor.display_text(cx),
27065                "line1\nline2\nline2",
27066                "Duplicating last line upward should create duplicate above, not on same line"
27067            );
27068
27069            assert_eq!(
27070                editor
27071                    .selections
27072                    .display_ranges(&editor.display_snapshot(cx)),
27073                vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)],
27074                "Selection should move to the duplicated line"
27075            );
27076        })
27077        .unwrap();
27078}
27079
27080#[gpui::test]
27081async fn test_copy_line_without_trailing_newline(cx: &mut TestAppContext) {
27082    init_test(cx, |_| {});
27083
27084    let mut cx = EditorTestContext::new(cx).await;
27085
27086    cx.set_state("line1\nline2ˇ");
27087
27088    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
27089
27090    let clipboard_text = cx
27091        .read_from_clipboard()
27092        .and_then(|item| item.text().as_deref().map(str::to_string));
27093
27094    assert_eq!(
27095        clipboard_text,
27096        Some("line2\n".to_string()),
27097        "Copying a line without trailing newline should include a newline"
27098    );
27099
27100    cx.set_state("line1\nˇ");
27101
27102    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
27103
27104    cx.assert_editor_state("line1\nline2\nˇ");
27105}
27106
27107#[gpui::test]
27108async fn test_end_of_editor_context(cx: &mut TestAppContext) {
27109    init_test(cx, |_| {});
27110
27111    let mut cx = EditorTestContext::new(cx).await;
27112
27113    cx.set_state("line1\nline2ˇ");
27114    cx.update_editor(|e, window, cx| {
27115        e.set_mode(EditorMode::SingleLine);
27116        assert!(e.key_context(window, cx).contains("end_of_input"));
27117    });
27118    cx.set_state("ˇline1\nline2");
27119    cx.update_editor(|e, window, cx| {
27120        assert!(!e.key_context(window, cx).contains("end_of_input"));
27121    });
27122    cx.set_state("line1ˇ\nline2");
27123    cx.update_editor(|e, window, cx| {
27124        assert!(!e.key_context(window, cx).contains("end_of_input"));
27125    });
27126}
27127
27128#[gpui::test]
27129async fn test_sticky_scroll(cx: &mut TestAppContext) {
27130    init_test(cx, |_| {});
27131    let mut cx = EditorTestContext::new(cx).await;
27132
27133    let buffer = indoc! {"
27134            ˇfn foo() {
27135                let abc = 123;
27136            }
27137            struct Bar;
27138            impl Bar {
27139                fn new() -> Self {
27140                    Self
27141                }
27142            }
27143            fn baz() {
27144            }
27145        "};
27146    cx.set_state(&buffer);
27147
27148    cx.update_editor(|e, _, cx| {
27149        e.buffer()
27150            .read(cx)
27151            .as_singleton()
27152            .unwrap()
27153            .update(cx, |buffer, cx| {
27154                buffer.set_language(Some(rust_lang()), cx);
27155            })
27156    });
27157
27158    let mut sticky_headers = |offset: ScrollOffset| {
27159        cx.update_editor(|e, window, cx| {
27160            e.scroll(gpui::Point { x: 0., y: offset }, None, window, cx);
27161            EditorElement::sticky_headers(&e, &e.snapshot(window, cx), cx)
27162                .into_iter()
27163                .map(
27164                    |StickyHeader {
27165                         start_point,
27166                         offset,
27167                         ..
27168                     }| { (start_point, offset) },
27169                )
27170                .collect::<Vec<_>>()
27171        })
27172    };
27173
27174    let fn_foo = Point { row: 0, column: 0 };
27175    let impl_bar = Point { row: 4, column: 0 };
27176    let fn_new = Point { row: 5, column: 4 };
27177
27178    assert_eq!(sticky_headers(0.0), vec![]);
27179    assert_eq!(sticky_headers(0.5), vec![(fn_foo, 0.0)]);
27180    assert_eq!(sticky_headers(1.0), vec![(fn_foo, 0.0)]);
27181    assert_eq!(sticky_headers(1.5), vec![(fn_foo, -0.5)]);
27182    assert_eq!(sticky_headers(2.0), vec![]);
27183    assert_eq!(sticky_headers(2.5), vec![]);
27184    assert_eq!(sticky_headers(3.0), vec![]);
27185    assert_eq!(sticky_headers(3.5), vec![]);
27186    assert_eq!(sticky_headers(4.0), vec![]);
27187    assert_eq!(sticky_headers(4.5), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
27188    assert_eq!(sticky_headers(5.0), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
27189    assert_eq!(sticky_headers(5.5), vec![(impl_bar, 0.0), (fn_new, 0.5)]);
27190    assert_eq!(sticky_headers(6.0), vec![(impl_bar, 0.0)]);
27191    assert_eq!(sticky_headers(6.5), vec![(impl_bar, 0.0)]);
27192    assert_eq!(sticky_headers(7.0), vec![(impl_bar, 0.0)]);
27193    assert_eq!(sticky_headers(7.5), vec![(impl_bar, -0.5)]);
27194    assert_eq!(sticky_headers(8.0), vec![]);
27195    assert_eq!(sticky_headers(8.5), vec![]);
27196    assert_eq!(sticky_headers(9.0), vec![]);
27197    assert_eq!(sticky_headers(9.5), vec![]);
27198    assert_eq!(sticky_headers(10.0), vec![]);
27199}
27200
27201#[gpui::test]
27202async fn test_scroll_by_clicking_sticky_header(cx: &mut TestAppContext) {
27203    init_test(cx, |_| {});
27204    cx.update(|cx| {
27205        SettingsStore::update_global(cx, |store, cx| {
27206            store.update_user_settings(cx, |settings| {
27207                settings.editor.sticky_scroll = Some(settings::StickyScrollContent {
27208                    enabled: Some(true),
27209                })
27210            });
27211        });
27212    });
27213    let mut cx = EditorTestContext::new(cx).await;
27214
27215    let line_height = cx.editor(|editor, window, _cx| {
27216        editor
27217            .style()
27218            .unwrap()
27219            .text
27220            .line_height_in_pixels(window.rem_size())
27221    });
27222
27223    let buffer = indoc! {"
27224            ˇfn foo() {
27225                let abc = 123;
27226            }
27227            struct Bar;
27228            impl Bar {
27229                fn new() -> Self {
27230                    Self
27231                }
27232            }
27233            fn baz() {
27234            }
27235        "};
27236    cx.set_state(&buffer);
27237
27238    cx.update_editor(|e, _, cx| {
27239        e.buffer()
27240            .read(cx)
27241            .as_singleton()
27242            .unwrap()
27243            .update(cx, |buffer, cx| {
27244                buffer.set_language(Some(rust_lang()), cx);
27245            })
27246    });
27247
27248    let fn_foo = || empty_range(0, 0);
27249    let impl_bar = || empty_range(4, 0);
27250    let fn_new = || empty_range(5, 4);
27251
27252    let mut scroll_and_click = |scroll_offset: ScrollOffset, click_offset: ScrollOffset| {
27253        cx.update_editor(|e, window, cx| {
27254            e.scroll(
27255                gpui::Point {
27256                    x: 0.,
27257                    y: scroll_offset,
27258                },
27259                None,
27260                window,
27261                cx,
27262            );
27263        });
27264        cx.simulate_click(
27265            gpui::Point {
27266                x: px(0.),
27267                y: click_offset as f32 * line_height,
27268            },
27269            Modifiers::none(),
27270        );
27271        cx.update_editor(|e, _, cx| (e.scroll_position(cx), display_ranges(e, cx)))
27272    };
27273
27274    assert_eq!(
27275        scroll_and_click(
27276            4.5, // impl Bar is halfway off the screen
27277            0.0  // click top of screen
27278        ),
27279        // scrolled to impl Bar
27280        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
27281    );
27282
27283    assert_eq!(
27284        scroll_and_click(
27285            4.5,  // impl Bar is halfway off the screen
27286            0.25  // click middle of impl Bar
27287        ),
27288        // scrolled to impl Bar
27289        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
27290    );
27291
27292    assert_eq!(
27293        scroll_and_click(
27294            4.5, // impl Bar is halfway off the screen
27295            1.5  // click below impl Bar (e.g. fn new())
27296        ),
27297        // scrolled to fn new() - this is below the impl Bar header which has persisted
27298        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
27299    );
27300
27301    assert_eq!(
27302        scroll_and_click(
27303            5.5,  // fn new is halfway underneath impl Bar
27304            0.75  // click on the overlap of impl Bar and fn new()
27305        ),
27306        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
27307    );
27308
27309    assert_eq!(
27310        scroll_and_click(
27311            5.5,  // fn new is halfway underneath impl Bar
27312            1.25  // click on the visible part of fn new()
27313        ),
27314        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
27315    );
27316
27317    assert_eq!(
27318        scroll_and_click(
27319            1.5, // fn foo is halfway off the screen
27320            0.0  // click top of screen
27321        ),
27322        (gpui::Point { x: 0., y: 0. }, vec![fn_foo()])
27323    );
27324
27325    assert_eq!(
27326        scroll_and_click(
27327            1.5,  // fn foo is halfway off the screen
27328            0.75  // click visible part of let abc...
27329        )
27330        .0,
27331        // no change in scroll
27332        // we don't assert on the visible_range because if we clicked the gutter, our line is fully selected
27333        (gpui::Point { x: 0., y: 1.5 })
27334    );
27335}
27336
27337#[gpui::test]
27338async fn test_next_prev_reference(cx: &mut TestAppContext) {
27339    const CYCLE_POSITIONS: &[&'static str] = &[
27340        indoc! {"
27341            fn foo() {
27342                let ˇabc = 123;
27343                let x = abc + 1;
27344                let y = abc + 2;
27345                let z = abc + 2;
27346            }
27347        "},
27348        indoc! {"
27349            fn foo() {
27350                let abc = 123;
27351                let x = ˇabc + 1;
27352                let y = abc + 2;
27353                let z = abc + 2;
27354            }
27355        "},
27356        indoc! {"
27357            fn foo() {
27358                let abc = 123;
27359                let x = abc + 1;
27360                let y = ˇabc + 2;
27361                let z = abc + 2;
27362            }
27363        "},
27364        indoc! {"
27365            fn foo() {
27366                let abc = 123;
27367                let x = abc + 1;
27368                let y = abc + 2;
27369                let z = ˇabc + 2;
27370            }
27371        "},
27372    ];
27373
27374    init_test(cx, |_| {});
27375
27376    let mut cx = EditorLspTestContext::new_rust(
27377        lsp::ServerCapabilities {
27378            references_provider: Some(lsp::OneOf::Left(true)),
27379            ..Default::default()
27380        },
27381        cx,
27382    )
27383    .await;
27384
27385    // importantly, the cursor is in the middle
27386    cx.set_state(indoc! {"
27387        fn foo() {
27388            let aˇbc = 123;
27389            let x = abc + 1;
27390            let y = abc + 2;
27391            let z = abc + 2;
27392        }
27393    "});
27394
27395    let reference_ranges = [
27396        lsp::Position::new(1, 8),
27397        lsp::Position::new(2, 12),
27398        lsp::Position::new(3, 12),
27399        lsp::Position::new(4, 12),
27400    ]
27401    .map(|start| lsp::Range::new(start, lsp::Position::new(start.line, start.character + 3)));
27402
27403    cx.lsp
27404        .set_request_handler::<lsp::request::References, _, _>(move |params, _cx| async move {
27405            Ok(Some(
27406                reference_ranges
27407                    .map(|range| lsp::Location {
27408                        uri: params.text_document_position.text_document.uri.clone(),
27409                        range,
27410                    })
27411                    .to_vec(),
27412            ))
27413        });
27414
27415    let _move = async |direction, count, cx: &mut EditorLspTestContext| {
27416        cx.update_editor(|editor, window, cx| {
27417            editor.go_to_reference_before_or_after_position(direction, count, window, cx)
27418        })
27419        .unwrap()
27420        .await
27421        .unwrap()
27422    };
27423
27424    _move(Direction::Next, 1, &mut cx).await;
27425    cx.assert_editor_state(CYCLE_POSITIONS[1]);
27426
27427    _move(Direction::Next, 1, &mut cx).await;
27428    cx.assert_editor_state(CYCLE_POSITIONS[2]);
27429
27430    _move(Direction::Next, 1, &mut cx).await;
27431    cx.assert_editor_state(CYCLE_POSITIONS[3]);
27432
27433    // loops back to the start
27434    _move(Direction::Next, 1, &mut cx).await;
27435    cx.assert_editor_state(CYCLE_POSITIONS[0]);
27436
27437    // loops back to the end
27438    _move(Direction::Prev, 1, &mut cx).await;
27439    cx.assert_editor_state(CYCLE_POSITIONS[3]);
27440
27441    _move(Direction::Prev, 1, &mut cx).await;
27442    cx.assert_editor_state(CYCLE_POSITIONS[2]);
27443
27444    _move(Direction::Prev, 1, &mut cx).await;
27445    cx.assert_editor_state(CYCLE_POSITIONS[1]);
27446
27447    _move(Direction::Prev, 1, &mut cx).await;
27448    cx.assert_editor_state(CYCLE_POSITIONS[0]);
27449
27450    _move(Direction::Next, 3, &mut cx).await;
27451    cx.assert_editor_state(CYCLE_POSITIONS[3]);
27452
27453    _move(Direction::Prev, 2, &mut cx).await;
27454    cx.assert_editor_state(CYCLE_POSITIONS[1]);
27455}
27456
27457#[gpui::test]
27458async fn test_multibuffer_selections_with_folding(cx: &mut TestAppContext) {
27459    init_test(cx, |_| {});
27460
27461    let (editor, cx) = cx.add_window_view(|window, cx| {
27462        let multi_buffer = MultiBuffer::build_multi(
27463            [
27464                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
27465                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
27466            ],
27467            cx,
27468        );
27469        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
27470    });
27471
27472    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
27473    let buffer_ids = cx.multibuffer(|mb, _| mb.excerpt_buffer_ids());
27474
27475    cx.assert_excerpts_with_selections(indoc! {"
27476        [EXCERPT]
27477        ˇ1
27478        2
27479        3
27480        [EXCERPT]
27481        1
27482        2
27483        3
27484        "});
27485
27486    // Scenario 1: Unfolded buffers, position cursor on "2", select all matches, then insert
27487    cx.update_editor(|editor, window, cx| {
27488        editor.change_selections(None.into(), window, cx, |s| {
27489            s.select_ranges([2..3]);
27490        });
27491    });
27492    cx.assert_excerpts_with_selections(indoc! {"
27493        [EXCERPT]
27494        1
2749527496        3
27497        [EXCERPT]
27498        1
27499        2
27500        3
27501        "});
27502
27503    cx.update_editor(|editor, window, cx| {
27504        editor
27505            .select_all_matches(&SelectAllMatches, window, cx)
27506            .unwrap();
27507    });
27508    cx.assert_excerpts_with_selections(indoc! {"
27509        [EXCERPT]
27510        1
2751127512        3
27513        [EXCERPT]
27514        1
2751527516        3
27517        "});
27518
27519    cx.update_editor(|editor, window, cx| {
27520        editor.handle_input("X", window, cx);
27521    });
27522    cx.assert_excerpts_with_selections(indoc! {"
27523        [EXCERPT]
27524        1
2752527526        3
27527        [EXCERPT]
27528        1
2752927530        3
27531        "});
27532
27533    // Scenario 2: Select "2", then fold second buffer before insertion
27534    cx.update_multibuffer(|mb, cx| {
27535        for buffer_id in buffer_ids.iter() {
27536            let buffer = mb.buffer(*buffer_id).unwrap();
27537            buffer.update(cx, |buffer, cx| {
27538                buffer.edit([(0..buffer.len(), "1\n2\n3\n")], None, cx);
27539            });
27540        }
27541    });
27542
27543    // Select "2" and select all matches
27544    cx.update_editor(|editor, window, cx| {
27545        editor.change_selections(None.into(), window, cx, |s| {
27546            s.select_ranges([2..3]);
27547        });
27548        editor
27549            .select_all_matches(&SelectAllMatches, window, cx)
27550            .unwrap();
27551    });
27552
27553    // Fold second buffer - should remove selections from folded buffer
27554    cx.update_editor(|editor, _, cx| {
27555        editor.fold_buffer(buffer_ids[1], cx);
27556    });
27557    cx.assert_excerpts_with_selections(indoc! {"
27558        [EXCERPT]
27559        1
2756027561        3
27562        [EXCERPT]
27563        [FOLDED]
27564        "});
27565
27566    // Insert text - should only affect first buffer
27567    cx.update_editor(|editor, window, cx| {
27568        editor.handle_input("Y", window, cx);
27569    });
27570    cx.update_editor(|editor, _, cx| {
27571        editor.unfold_buffer(buffer_ids[1], cx);
27572    });
27573    cx.assert_excerpts_with_selections(indoc! {"
27574        [EXCERPT]
27575        1
2757627577        3
27578        [EXCERPT]
27579        1
27580        2
27581        3
27582        "});
27583
27584    // Scenario 3: Select "2", then fold first buffer before insertion
27585    cx.update_multibuffer(|mb, cx| {
27586        for buffer_id in buffer_ids.iter() {
27587            let buffer = mb.buffer(*buffer_id).unwrap();
27588            buffer.update(cx, |buffer, cx| {
27589                buffer.edit([(0..buffer.len(), "1\n2\n3\n")], None, cx);
27590            });
27591        }
27592    });
27593
27594    // Select "2" and select all matches
27595    cx.update_editor(|editor, window, cx| {
27596        editor.change_selections(None.into(), window, cx, |s| {
27597            s.select_ranges([2..3]);
27598        });
27599        editor
27600            .select_all_matches(&SelectAllMatches, window, cx)
27601            .unwrap();
27602    });
27603
27604    // Fold first buffer - should remove selections from folded buffer
27605    cx.update_editor(|editor, _, cx| {
27606        editor.fold_buffer(buffer_ids[0], cx);
27607    });
27608    cx.assert_excerpts_with_selections(indoc! {"
27609        [EXCERPT]
27610        [FOLDED]
27611        [EXCERPT]
27612        1
2761327614        3
27615        "});
27616
27617    // Insert text - should only affect second buffer
27618    cx.update_editor(|editor, window, cx| {
27619        editor.handle_input("Z", window, cx);
27620    });
27621    cx.update_editor(|editor, _, cx| {
27622        editor.unfold_buffer(buffer_ids[0], cx);
27623    });
27624    cx.assert_excerpts_with_selections(indoc! {"
27625        [EXCERPT]
27626        1
27627        2
27628        3
27629        [EXCERPT]
27630        1
2763127632        3
27633        "});
27634
27635    // Edge case scenario: fold all buffers, then try to insert
27636    cx.update_editor(|editor, _, cx| {
27637        editor.fold_buffer(buffer_ids[0], cx);
27638        editor.fold_buffer(buffer_ids[1], cx);
27639    });
27640    cx.assert_excerpts_with_selections(indoc! {"
27641        [EXCERPT]
27642        ˇ[FOLDED]
27643        [EXCERPT]
27644        [FOLDED]
27645        "});
27646
27647    // Insert should work via default selection
27648    cx.update_editor(|editor, window, cx| {
27649        editor.handle_input("W", window, cx);
27650    });
27651    cx.update_editor(|editor, _, cx| {
27652        editor.unfold_buffer(buffer_ids[0], cx);
27653        editor.unfold_buffer(buffer_ids[1], cx);
27654    });
27655    cx.assert_excerpts_with_selections(indoc! {"
27656        [EXCERPT]
27657        Wˇ1
27658        2
27659        3
27660        [EXCERPT]
27661        1
27662        Z
27663        3
27664        "});
27665}