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_snippet_with_multi_word_prefix(cx: &mut TestAppContext) {
11419    init_test(cx, |_| {});
11420
11421    let mut cx = EditorTestContext::new(cx).await;
11422    cx.update_editor(|editor, _, cx| {
11423        editor.project().unwrap().update(cx, |project, cx| {
11424            project.snippets().update(cx, |snippets, _cx| {
11425                let snippet = project::snippet_provider::Snippet {
11426                    prefix: vec!["multi word".to_string()],
11427                    body: "this is many words".to_string(),
11428                    description: Some("description".to_string()),
11429                    name: "multi-word snippet test".to_string(),
11430                };
11431                snippets.add_snippet_for_test(
11432                    None,
11433                    PathBuf::from("test_snippets.json"),
11434                    vec![Arc::new(snippet)],
11435                );
11436            });
11437        })
11438    });
11439
11440    for (input_to_simulate, should_match_snippet) in [
11441        ("m", true),
11442        ("m ", true),
11443        ("m w", true),
11444        ("aa m w", true),
11445        ("aa m g", false),
11446    ] {
11447        cx.set_state("ˇ");
11448        cx.simulate_input(input_to_simulate); // fails correctly
11449
11450        cx.update_editor(|editor, _, _| {
11451            let Some(CodeContextMenu::Completions(context_menu)) = &*editor.context_menu.borrow()
11452            else {
11453                assert!(!should_match_snippet); // no completions! don't even show the menu
11454                return;
11455            };
11456            assert!(context_menu.visible());
11457            let completions = context_menu.completions.borrow();
11458
11459            assert_eq!(!completions.is_empty(), should_match_snippet);
11460        });
11461    }
11462}
11463
11464#[gpui::test]
11465async fn test_document_format_during_save(cx: &mut TestAppContext) {
11466    init_test(cx, |_| {});
11467
11468    let fs = FakeFs::new(cx.executor());
11469    fs.insert_file(path!("/file.rs"), Default::default()).await;
11470
11471    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
11472
11473    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11474    language_registry.add(rust_lang());
11475    let mut fake_servers = language_registry.register_fake_lsp(
11476        "Rust",
11477        FakeLspAdapter {
11478            capabilities: lsp::ServerCapabilities {
11479                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11480                ..Default::default()
11481            },
11482            ..Default::default()
11483        },
11484    );
11485
11486    let buffer = project
11487        .update(cx, |project, cx| {
11488            project.open_local_buffer(path!("/file.rs"), cx)
11489        })
11490        .await
11491        .unwrap();
11492
11493    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11494    let (editor, cx) = cx.add_window_view(|window, cx| {
11495        build_editor_with_project(project.clone(), buffer, window, cx)
11496    });
11497    editor.update_in(cx, |editor, window, cx| {
11498        editor.set_text("one\ntwo\nthree\n", window, cx)
11499    });
11500    assert!(cx.read(|cx| editor.is_dirty(cx)));
11501
11502    cx.executor().start_waiting();
11503    let fake_server = fake_servers.next().await.unwrap();
11504
11505    {
11506        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11507            move |params, _| async move {
11508                assert_eq!(
11509                    params.text_document.uri,
11510                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11511                );
11512                assert_eq!(params.options.tab_size, 4);
11513                Ok(Some(vec![lsp::TextEdit::new(
11514                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11515                    ", ".to_string(),
11516                )]))
11517            },
11518        );
11519        let save = editor
11520            .update_in(cx, |editor, window, cx| {
11521                editor.save(
11522                    SaveOptions {
11523                        format: true,
11524                        autosave: false,
11525                    },
11526                    project.clone(),
11527                    window,
11528                    cx,
11529                )
11530            })
11531            .unwrap();
11532        cx.executor().start_waiting();
11533        save.await;
11534
11535        assert_eq!(
11536            editor.update(cx, |editor, cx| editor.text(cx)),
11537            "one, two\nthree\n"
11538        );
11539        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11540    }
11541
11542    {
11543        editor.update_in(cx, |editor, window, cx| {
11544            editor.set_text("one\ntwo\nthree\n", window, cx)
11545        });
11546        assert!(cx.read(|cx| editor.is_dirty(cx)));
11547
11548        // Ensure we can still save even if formatting hangs.
11549        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11550            move |params, _| async move {
11551                assert_eq!(
11552                    params.text_document.uri,
11553                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11554                );
11555                futures::future::pending::<()>().await;
11556                unreachable!()
11557            },
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().advance_clock(super::FORMAT_TIMEOUT);
11573        cx.executor().start_waiting();
11574        save.await;
11575        assert_eq!(
11576            editor.update(cx, |editor, cx| editor.text(cx)),
11577            "one\ntwo\nthree\n"
11578        );
11579    }
11580
11581    // Set rust language override and assert overridden tabsize is sent to language server
11582    update_test_language_settings(cx, |settings| {
11583        settings.languages.0.insert(
11584            "Rust".into(),
11585            LanguageSettingsContent {
11586                tab_size: NonZeroU32::new(8),
11587                ..Default::default()
11588            },
11589        );
11590    });
11591
11592    {
11593        editor.update_in(cx, |editor, window, cx| {
11594            editor.set_text("somehting_new\n", window, cx)
11595        });
11596        assert!(cx.read(|cx| editor.is_dirty(cx)));
11597        let _formatting_request_signal = fake_server
11598            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
11599                assert_eq!(
11600                    params.text_document.uri,
11601                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11602                );
11603                assert_eq!(params.options.tab_size, 8);
11604                Ok(Some(vec![]))
11605            });
11606        let save = editor
11607            .update_in(cx, |editor, window, cx| {
11608                editor.save(
11609                    SaveOptions {
11610                        format: true,
11611                        autosave: false,
11612                    },
11613                    project.clone(),
11614                    window,
11615                    cx,
11616                )
11617            })
11618            .unwrap();
11619        cx.executor().start_waiting();
11620        save.await;
11621    }
11622}
11623
11624#[gpui::test]
11625async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
11626    init_test(cx, |settings| {
11627        settings.defaults.ensure_final_newline_on_save = Some(false);
11628    });
11629
11630    let fs = FakeFs::new(cx.executor());
11631    fs.insert_file(path!("/file.txt"), "foo".into()).await;
11632
11633    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
11634
11635    let buffer = project
11636        .update(cx, |project, cx| {
11637            project.open_local_buffer(path!("/file.txt"), cx)
11638        })
11639        .await
11640        .unwrap();
11641
11642    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11643    let (editor, cx) = cx.add_window_view(|window, cx| {
11644        build_editor_with_project(project.clone(), buffer, window, cx)
11645    });
11646    editor.update_in(cx, |editor, window, cx| {
11647        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
11648            s.select_ranges([0..0])
11649        });
11650    });
11651    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11652
11653    editor.update_in(cx, |editor, window, cx| {
11654        editor.handle_input("\n", window, cx)
11655    });
11656    cx.run_until_parked();
11657    save(&editor, &project, cx).await;
11658    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11659
11660    editor.update_in(cx, |editor, window, cx| {
11661        editor.undo(&Default::default(), window, cx);
11662    });
11663    save(&editor, &project, cx).await;
11664    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11665
11666    editor.update_in(cx, |editor, window, cx| {
11667        editor.redo(&Default::default(), window, cx);
11668    });
11669    cx.run_until_parked();
11670    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11671
11672    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
11673        let save = editor
11674            .update_in(cx, |editor, window, cx| {
11675                editor.save(
11676                    SaveOptions {
11677                        format: true,
11678                        autosave: false,
11679                    },
11680                    project.clone(),
11681                    window,
11682                    cx,
11683                )
11684            })
11685            .unwrap();
11686        cx.executor().start_waiting();
11687        save.await;
11688        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11689    }
11690}
11691
11692#[gpui::test]
11693async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
11694    init_test(cx, |_| {});
11695
11696    let cols = 4;
11697    let rows = 10;
11698    let sample_text_1 = sample_text(rows, cols, 'a');
11699    assert_eq!(
11700        sample_text_1,
11701        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11702    );
11703    let sample_text_2 = sample_text(rows, cols, 'l');
11704    assert_eq!(
11705        sample_text_2,
11706        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11707    );
11708    let sample_text_3 = sample_text(rows, cols, 'v');
11709    assert_eq!(
11710        sample_text_3,
11711        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11712    );
11713
11714    let fs = FakeFs::new(cx.executor());
11715    fs.insert_tree(
11716        path!("/a"),
11717        json!({
11718            "main.rs": sample_text_1,
11719            "other.rs": sample_text_2,
11720            "lib.rs": sample_text_3,
11721        }),
11722    )
11723    .await;
11724
11725    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11726    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11727    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11728
11729    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11730    language_registry.add(rust_lang());
11731    let mut fake_servers = language_registry.register_fake_lsp(
11732        "Rust",
11733        FakeLspAdapter {
11734            capabilities: lsp::ServerCapabilities {
11735                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11736                ..Default::default()
11737            },
11738            ..Default::default()
11739        },
11740    );
11741
11742    let worktree = project.update(cx, |project, cx| {
11743        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
11744        assert_eq!(worktrees.len(), 1);
11745        worktrees.pop().unwrap()
11746    });
11747    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11748
11749    let buffer_1 = project
11750        .update(cx, |project, cx| {
11751            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
11752        })
11753        .await
11754        .unwrap();
11755    let buffer_2 = project
11756        .update(cx, |project, cx| {
11757            project.open_buffer((worktree_id, rel_path("other.rs")), cx)
11758        })
11759        .await
11760        .unwrap();
11761    let buffer_3 = project
11762        .update(cx, |project, cx| {
11763            project.open_buffer((worktree_id, rel_path("lib.rs")), cx)
11764        })
11765        .await
11766        .unwrap();
11767
11768    let multi_buffer = cx.new(|cx| {
11769        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11770        multi_buffer.push_excerpts(
11771            buffer_1.clone(),
11772            [
11773                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11774                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11775                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11776            ],
11777            cx,
11778        );
11779        multi_buffer.push_excerpts(
11780            buffer_2.clone(),
11781            [
11782                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11783                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11784                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11785            ],
11786            cx,
11787        );
11788        multi_buffer.push_excerpts(
11789            buffer_3.clone(),
11790            [
11791                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11792                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11793                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11794            ],
11795            cx,
11796        );
11797        multi_buffer
11798    });
11799    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
11800        Editor::new(
11801            EditorMode::full(),
11802            multi_buffer,
11803            Some(project.clone()),
11804            window,
11805            cx,
11806        )
11807    });
11808
11809    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11810        editor.change_selections(
11811            SelectionEffects::scroll(Autoscroll::Next),
11812            window,
11813            cx,
11814            |s| s.select_ranges(Some(1..2)),
11815        );
11816        editor.insert("|one|two|three|", window, cx);
11817    });
11818    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11819    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11820        editor.change_selections(
11821            SelectionEffects::scroll(Autoscroll::Next),
11822            window,
11823            cx,
11824            |s| s.select_ranges(Some(60..70)),
11825        );
11826        editor.insert("|four|five|six|", window, cx);
11827    });
11828    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11829
11830    // First two buffers should be edited, but not the third one.
11831    assert_eq!(
11832        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11833        "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}",
11834    );
11835    buffer_1.update(cx, |buffer, _| {
11836        assert!(buffer.is_dirty());
11837        assert_eq!(
11838            buffer.text(),
11839            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
11840        )
11841    });
11842    buffer_2.update(cx, |buffer, _| {
11843        assert!(buffer.is_dirty());
11844        assert_eq!(
11845            buffer.text(),
11846            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
11847        )
11848    });
11849    buffer_3.update(cx, |buffer, _| {
11850        assert!(!buffer.is_dirty());
11851        assert_eq!(buffer.text(), sample_text_3,)
11852    });
11853    cx.executor().run_until_parked();
11854
11855    cx.executor().start_waiting();
11856    let save = multi_buffer_editor
11857        .update_in(cx, |editor, window, cx| {
11858            editor.save(
11859                SaveOptions {
11860                    format: true,
11861                    autosave: false,
11862                },
11863                project.clone(),
11864                window,
11865                cx,
11866            )
11867        })
11868        .unwrap();
11869
11870    let fake_server = fake_servers.next().await.unwrap();
11871    fake_server
11872        .server
11873        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
11874            Ok(Some(vec![lsp::TextEdit::new(
11875                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11876                format!("[{} formatted]", params.text_document.uri),
11877            )]))
11878        })
11879        .detach();
11880    save.await;
11881
11882    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
11883    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
11884    assert_eq!(
11885        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11886        uri!(
11887            "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}"
11888        ),
11889    );
11890    buffer_1.update(cx, |buffer, _| {
11891        assert!(!buffer.is_dirty());
11892        assert_eq!(
11893            buffer.text(),
11894            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
11895        )
11896    });
11897    buffer_2.update(cx, |buffer, _| {
11898        assert!(!buffer.is_dirty());
11899        assert_eq!(
11900            buffer.text(),
11901            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
11902        )
11903    });
11904    buffer_3.update(cx, |buffer, _| {
11905        assert!(!buffer.is_dirty());
11906        assert_eq!(buffer.text(), sample_text_3,)
11907    });
11908}
11909
11910#[gpui::test]
11911async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
11912    init_test(cx, |_| {});
11913
11914    let fs = FakeFs::new(cx.executor());
11915    fs.insert_tree(
11916        path!("/dir"),
11917        json!({
11918            "file1.rs": "fn main() { println!(\"hello\"); }",
11919            "file2.rs": "fn test() { println!(\"test\"); }",
11920            "file3.rs": "fn other() { println!(\"other\"); }\n",
11921        }),
11922    )
11923    .await;
11924
11925    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
11926    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11927    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11928
11929    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11930    language_registry.add(rust_lang());
11931
11932    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
11933    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11934
11935    // Open three buffers
11936    let buffer_1 = project
11937        .update(cx, |project, cx| {
11938            project.open_buffer((worktree_id, rel_path("file1.rs")), cx)
11939        })
11940        .await
11941        .unwrap();
11942    let buffer_2 = project
11943        .update(cx, |project, cx| {
11944            project.open_buffer((worktree_id, rel_path("file2.rs")), cx)
11945        })
11946        .await
11947        .unwrap();
11948    let buffer_3 = project
11949        .update(cx, |project, cx| {
11950            project.open_buffer((worktree_id, rel_path("file3.rs")), cx)
11951        })
11952        .await
11953        .unwrap();
11954
11955    // Create a multi-buffer with all three buffers
11956    let multi_buffer = cx.new(|cx| {
11957        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11958        multi_buffer.push_excerpts(
11959            buffer_1.clone(),
11960            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11961            cx,
11962        );
11963        multi_buffer.push_excerpts(
11964            buffer_2.clone(),
11965            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11966            cx,
11967        );
11968        multi_buffer.push_excerpts(
11969            buffer_3.clone(),
11970            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11971            cx,
11972        );
11973        multi_buffer
11974    });
11975
11976    let editor = cx.new_window_entity(|window, cx| {
11977        Editor::new(
11978            EditorMode::full(),
11979            multi_buffer,
11980            Some(project.clone()),
11981            window,
11982            cx,
11983        )
11984    });
11985
11986    // Edit only the first buffer
11987    editor.update_in(cx, |editor, window, cx| {
11988        editor.change_selections(
11989            SelectionEffects::scroll(Autoscroll::Next),
11990            window,
11991            cx,
11992            |s| s.select_ranges(Some(10..10)),
11993        );
11994        editor.insert("// edited", window, cx);
11995    });
11996
11997    // Verify that only buffer 1 is dirty
11998    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
11999    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12000    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12001
12002    // Get write counts after file creation (files were created with initial content)
12003    // We expect each file to have been written once during creation
12004    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
12005    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
12006    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
12007
12008    // Perform autosave
12009    let save_task = editor.update_in(cx, |editor, window, cx| {
12010        editor.save(
12011            SaveOptions {
12012                format: true,
12013                autosave: true,
12014            },
12015            project.clone(),
12016            window,
12017            cx,
12018        )
12019    });
12020    save_task.await.unwrap();
12021
12022    // Only the dirty buffer should have been saved
12023    assert_eq!(
12024        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
12025        1,
12026        "Buffer 1 was dirty, so it should have been written once during autosave"
12027    );
12028    assert_eq!(
12029        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
12030        0,
12031        "Buffer 2 was clean, so it should not have been written during autosave"
12032    );
12033    assert_eq!(
12034        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
12035        0,
12036        "Buffer 3 was clean, so it should not have been written during autosave"
12037    );
12038
12039    // Verify buffer states after autosave
12040    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12041    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12042    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12043
12044    // Now perform a manual save (format = true)
12045    let save_task = editor.update_in(cx, |editor, window, cx| {
12046        editor.save(
12047            SaveOptions {
12048                format: true,
12049                autosave: false,
12050            },
12051            project.clone(),
12052            window,
12053            cx,
12054        )
12055    });
12056    save_task.await.unwrap();
12057
12058    // During manual save, clean buffers don't get written to disk
12059    // They just get did_save called for language server notifications
12060    assert_eq!(
12061        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
12062        1,
12063        "Buffer 1 should only have been written once total (during autosave, not manual save)"
12064    );
12065    assert_eq!(
12066        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
12067        0,
12068        "Buffer 2 should not have been written at all"
12069    );
12070    assert_eq!(
12071        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
12072        0,
12073        "Buffer 3 should not have been written at all"
12074    );
12075}
12076
12077async fn setup_range_format_test(
12078    cx: &mut TestAppContext,
12079) -> (
12080    Entity<Project>,
12081    Entity<Editor>,
12082    &mut gpui::VisualTestContext,
12083    lsp::FakeLanguageServer,
12084) {
12085    init_test(cx, |_| {});
12086
12087    let fs = FakeFs::new(cx.executor());
12088    fs.insert_file(path!("/file.rs"), Default::default()).await;
12089
12090    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12091
12092    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12093    language_registry.add(rust_lang());
12094    let mut fake_servers = language_registry.register_fake_lsp(
12095        "Rust",
12096        FakeLspAdapter {
12097            capabilities: lsp::ServerCapabilities {
12098                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
12099                ..lsp::ServerCapabilities::default()
12100            },
12101            ..FakeLspAdapter::default()
12102        },
12103    );
12104
12105    let buffer = project
12106        .update(cx, |project, cx| {
12107            project.open_local_buffer(path!("/file.rs"), cx)
12108        })
12109        .await
12110        .unwrap();
12111
12112    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12113    let (editor, cx) = cx.add_window_view(|window, cx| {
12114        build_editor_with_project(project.clone(), buffer, window, cx)
12115    });
12116
12117    cx.executor().start_waiting();
12118    let fake_server = fake_servers.next().await.unwrap();
12119
12120    (project, editor, cx, fake_server)
12121}
12122
12123#[gpui::test]
12124async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
12125    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12126
12127    editor.update_in(cx, |editor, window, cx| {
12128        editor.set_text("one\ntwo\nthree\n", window, cx)
12129    });
12130    assert!(cx.read(|cx| editor.is_dirty(cx)));
12131
12132    let save = editor
12133        .update_in(cx, |editor, window, cx| {
12134            editor.save(
12135                SaveOptions {
12136                    format: true,
12137                    autosave: false,
12138                },
12139                project.clone(),
12140                window,
12141                cx,
12142            )
12143        })
12144        .unwrap();
12145    fake_server
12146        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
12147            assert_eq!(
12148                params.text_document.uri,
12149                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12150            );
12151            assert_eq!(params.options.tab_size, 4);
12152            Ok(Some(vec![lsp::TextEdit::new(
12153                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12154                ", ".to_string(),
12155            )]))
12156        })
12157        .next()
12158        .await;
12159    cx.executor().start_waiting();
12160    save.await;
12161    assert_eq!(
12162        editor.update(cx, |editor, cx| editor.text(cx)),
12163        "one, two\nthree\n"
12164    );
12165    assert!(!cx.read(|cx| editor.is_dirty(cx)));
12166}
12167
12168#[gpui::test]
12169async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
12170    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12171
12172    editor.update_in(cx, |editor, window, cx| {
12173        editor.set_text("one\ntwo\nthree\n", window, cx)
12174    });
12175    assert!(cx.read(|cx| editor.is_dirty(cx)));
12176
12177    // Test that save still works when formatting hangs
12178    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
12179        move |params, _| async move {
12180            assert_eq!(
12181                params.text_document.uri,
12182                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12183            );
12184            futures::future::pending::<()>().await;
12185            unreachable!()
12186        },
12187    );
12188    let save = editor
12189        .update_in(cx, |editor, window, cx| {
12190            editor.save(
12191                SaveOptions {
12192                    format: true,
12193                    autosave: false,
12194                },
12195                project.clone(),
12196                window,
12197                cx,
12198            )
12199        })
12200        .unwrap();
12201    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12202    cx.executor().start_waiting();
12203    save.await;
12204    assert_eq!(
12205        editor.update(cx, |editor, cx| editor.text(cx)),
12206        "one\ntwo\nthree\n"
12207    );
12208    assert!(!cx.read(|cx| editor.is_dirty(cx)));
12209}
12210
12211#[gpui::test]
12212async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
12213    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12214
12215    // Buffer starts clean, no formatting should be requested
12216    let save = editor
12217        .update_in(cx, |editor, window, cx| {
12218            editor.save(
12219                SaveOptions {
12220                    format: false,
12221                    autosave: false,
12222                },
12223                project.clone(),
12224                window,
12225                cx,
12226            )
12227        })
12228        .unwrap();
12229    let _pending_format_request = fake_server
12230        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
12231            panic!("Should not be invoked");
12232        })
12233        .next();
12234    cx.executor().start_waiting();
12235    save.await;
12236    cx.run_until_parked();
12237}
12238
12239#[gpui::test]
12240async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
12241    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12242
12243    // Set Rust language override and assert overridden tabsize is sent to language server
12244    update_test_language_settings(cx, |settings| {
12245        settings.languages.0.insert(
12246            "Rust".into(),
12247            LanguageSettingsContent {
12248                tab_size: NonZeroU32::new(8),
12249                ..Default::default()
12250            },
12251        );
12252    });
12253
12254    editor.update_in(cx, |editor, window, cx| {
12255        editor.set_text("something_new\n", window, cx)
12256    });
12257    assert!(cx.read(|cx| editor.is_dirty(cx)));
12258    let save = editor
12259        .update_in(cx, |editor, window, cx| {
12260            editor.save(
12261                SaveOptions {
12262                    format: true,
12263                    autosave: false,
12264                },
12265                project.clone(),
12266                window,
12267                cx,
12268            )
12269        })
12270        .unwrap();
12271    fake_server
12272        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
12273            assert_eq!(
12274                params.text_document.uri,
12275                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12276            );
12277            assert_eq!(params.options.tab_size, 8);
12278            Ok(Some(Vec::new()))
12279        })
12280        .next()
12281        .await;
12282    save.await;
12283}
12284
12285#[gpui::test]
12286async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
12287    init_test(cx, |settings| {
12288        settings.defaults.formatter = Some(FormatterList::Single(Formatter::LanguageServer(
12289            settings::LanguageServerFormatterSpecifier::Current,
12290        )))
12291    });
12292
12293    let fs = FakeFs::new(cx.executor());
12294    fs.insert_file(path!("/file.rs"), Default::default()).await;
12295
12296    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12297
12298    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12299    language_registry.add(Arc::new(Language::new(
12300        LanguageConfig {
12301            name: "Rust".into(),
12302            matcher: LanguageMatcher {
12303                path_suffixes: vec!["rs".to_string()],
12304                ..Default::default()
12305            },
12306            ..LanguageConfig::default()
12307        },
12308        Some(tree_sitter_rust::LANGUAGE.into()),
12309    )));
12310    update_test_language_settings(cx, |settings| {
12311        // Enable Prettier formatting for the same buffer, and ensure
12312        // LSP is called instead of Prettier.
12313        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12314    });
12315    let mut fake_servers = language_registry.register_fake_lsp(
12316        "Rust",
12317        FakeLspAdapter {
12318            capabilities: lsp::ServerCapabilities {
12319                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12320                ..Default::default()
12321            },
12322            ..Default::default()
12323        },
12324    );
12325
12326    let buffer = project
12327        .update(cx, |project, cx| {
12328            project.open_local_buffer(path!("/file.rs"), cx)
12329        })
12330        .await
12331        .unwrap();
12332
12333    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12334    let (editor, cx) = cx.add_window_view(|window, cx| {
12335        build_editor_with_project(project.clone(), buffer, window, cx)
12336    });
12337    editor.update_in(cx, |editor, window, cx| {
12338        editor.set_text("one\ntwo\nthree\n", window, cx)
12339    });
12340
12341    cx.executor().start_waiting();
12342    let fake_server = fake_servers.next().await.unwrap();
12343
12344    let format = editor
12345        .update_in(cx, |editor, window, cx| {
12346            editor.perform_format(
12347                project.clone(),
12348                FormatTrigger::Manual,
12349                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12350                window,
12351                cx,
12352            )
12353        })
12354        .unwrap();
12355    fake_server
12356        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
12357            assert_eq!(
12358                params.text_document.uri,
12359                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12360            );
12361            assert_eq!(params.options.tab_size, 4);
12362            Ok(Some(vec![lsp::TextEdit::new(
12363                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12364                ", ".to_string(),
12365            )]))
12366        })
12367        .next()
12368        .await;
12369    cx.executor().start_waiting();
12370    format.await;
12371    assert_eq!(
12372        editor.update(cx, |editor, cx| editor.text(cx)),
12373        "one, two\nthree\n"
12374    );
12375
12376    editor.update_in(cx, |editor, window, cx| {
12377        editor.set_text("one\ntwo\nthree\n", window, cx)
12378    });
12379    // Ensure we don't lock if formatting hangs.
12380    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12381        move |params, _| async move {
12382            assert_eq!(
12383                params.text_document.uri,
12384                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12385            );
12386            futures::future::pending::<()>().await;
12387            unreachable!()
12388        },
12389    );
12390    let format = editor
12391        .update_in(cx, |editor, window, cx| {
12392            editor.perform_format(
12393                project,
12394                FormatTrigger::Manual,
12395                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12396                window,
12397                cx,
12398            )
12399        })
12400        .unwrap();
12401    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12402    cx.executor().start_waiting();
12403    format.await;
12404    assert_eq!(
12405        editor.update(cx, |editor, cx| editor.text(cx)),
12406        "one\ntwo\nthree\n"
12407    );
12408}
12409
12410#[gpui::test]
12411async fn test_multiple_formatters(cx: &mut TestAppContext) {
12412    init_test(cx, |settings| {
12413        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
12414        settings.defaults.formatter = Some(FormatterList::Vec(vec![
12415            Formatter::LanguageServer(settings::LanguageServerFormatterSpecifier::Current),
12416            Formatter::CodeAction("code-action-1".into()),
12417            Formatter::CodeAction("code-action-2".into()),
12418        ]))
12419    });
12420
12421    let fs = FakeFs::new(cx.executor());
12422    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
12423        .await;
12424
12425    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12426    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12427    language_registry.add(rust_lang());
12428
12429    let mut fake_servers = language_registry.register_fake_lsp(
12430        "Rust",
12431        FakeLspAdapter {
12432            capabilities: lsp::ServerCapabilities {
12433                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12434                execute_command_provider: Some(lsp::ExecuteCommandOptions {
12435                    commands: vec!["the-command-for-code-action-1".into()],
12436                    ..Default::default()
12437                }),
12438                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12439                ..Default::default()
12440            },
12441            ..Default::default()
12442        },
12443    );
12444
12445    let buffer = project
12446        .update(cx, |project, cx| {
12447            project.open_local_buffer(path!("/file.rs"), cx)
12448        })
12449        .await
12450        .unwrap();
12451
12452    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12453    let (editor, cx) = cx.add_window_view(|window, cx| {
12454        build_editor_with_project(project.clone(), buffer, window, cx)
12455    });
12456
12457    cx.executor().start_waiting();
12458
12459    let fake_server = fake_servers.next().await.unwrap();
12460    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12461        move |_params, _| async move {
12462            Ok(Some(vec![lsp::TextEdit::new(
12463                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12464                "applied-formatting\n".to_string(),
12465            )]))
12466        },
12467    );
12468    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12469        move |params, _| async move {
12470            let requested_code_actions = params.context.only.expect("Expected code action request");
12471            assert_eq!(requested_code_actions.len(), 1);
12472
12473            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
12474            let code_action = match requested_code_actions[0].as_str() {
12475                "code-action-1" => lsp::CodeAction {
12476                    kind: Some("code-action-1".into()),
12477                    edit: Some(lsp::WorkspaceEdit::new(
12478                        [(
12479                            uri,
12480                            vec![lsp::TextEdit::new(
12481                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12482                                "applied-code-action-1-edit\n".to_string(),
12483                            )],
12484                        )]
12485                        .into_iter()
12486                        .collect(),
12487                    )),
12488                    command: Some(lsp::Command {
12489                        command: "the-command-for-code-action-1".into(),
12490                        ..Default::default()
12491                    }),
12492                    ..Default::default()
12493                },
12494                "code-action-2" => lsp::CodeAction {
12495                    kind: Some("code-action-2".into()),
12496                    edit: Some(lsp::WorkspaceEdit::new(
12497                        [(
12498                            uri,
12499                            vec![lsp::TextEdit::new(
12500                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12501                                "applied-code-action-2-edit\n".to_string(),
12502                            )],
12503                        )]
12504                        .into_iter()
12505                        .collect(),
12506                    )),
12507                    ..Default::default()
12508                },
12509                req => panic!("Unexpected code action request: {:?}", req),
12510            };
12511            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12512                code_action,
12513            )]))
12514        },
12515    );
12516
12517    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
12518        move |params, _| async move { Ok(params) }
12519    });
12520
12521    let command_lock = Arc::new(futures::lock::Mutex::new(()));
12522    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
12523        let fake = fake_server.clone();
12524        let lock = command_lock.clone();
12525        move |params, _| {
12526            assert_eq!(params.command, "the-command-for-code-action-1");
12527            let fake = fake.clone();
12528            let lock = lock.clone();
12529            async move {
12530                lock.lock().await;
12531                fake.server
12532                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
12533                        label: None,
12534                        edit: lsp::WorkspaceEdit {
12535                            changes: Some(
12536                                [(
12537                                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
12538                                    vec![lsp::TextEdit {
12539                                        range: lsp::Range::new(
12540                                            lsp::Position::new(0, 0),
12541                                            lsp::Position::new(0, 0),
12542                                        ),
12543                                        new_text: "applied-code-action-1-command\n".into(),
12544                                    }],
12545                                )]
12546                                .into_iter()
12547                                .collect(),
12548                            ),
12549                            ..Default::default()
12550                        },
12551                    })
12552                    .await
12553                    .into_response()
12554                    .unwrap();
12555                Ok(Some(json!(null)))
12556            }
12557        }
12558    });
12559
12560    cx.executor().start_waiting();
12561    editor
12562        .update_in(cx, |editor, window, cx| {
12563            editor.perform_format(
12564                project.clone(),
12565                FormatTrigger::Manual,
12566                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12567                window,
12568                cx,
12569            )
12570        })
12571        .unwrap()
12572        .await;
12573    editor.update(cx, |editor, cx| {
12574        assert_eq!(
12575            editor.text(cx),
12576            r#"
12577                applied-code-action-2-edit
12578                applied-code-action-1-command
12579                applied-code-action-1-edit
12580                applied-formatting
12581                one
12582                two
12583                three
12584            "#
12585            .unindent()
12586        );
12587    });
12588
12589    editor.update_in(cx, |editor, window, cx| {
12590        editor.undo(&Default::default(), window, cx);
12591        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12592    });
12593
12594    // Perform a manual edit while waiting for an LSP command
12595    // that's being run as part of a formatting code action.
12596    let lock_guard = command_lock.lock().await;
12597    let format = editor
12598        .update_in(cx, |editor, window, cx| {
12599            editor.perform_format(
12600                project.clone(),
12601                FormatTrigger::Manual,
12602                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12603                window,
12604                cx,
12605            )
12606        })
12607        .unwrap();
12608    cx.run_until_parked();
12609    editor.update(cx, |editor, cx| {
12610        assert_eq!(
12611            editor.text(cx),
12612            r#"
12613                applied-code-action-1-edit
12614                applied-formatting
12615                one
12616                two
12617                three
12618            "#
12619            .unindent()
12620        );
12621
12622        editor.buffer.update(cx, |buffer, cx| {
12623            let ix = buffer.len(cx);
12624            buffer.edit([(ix..ix, "edited\n")], None, cx);
12625        });
12626    });
12627
12628    // Allow the LSP command to proceed. Because the buffer was edited,
12629    // the second code action will not be run.
12630    drop(lock_guard);
12631    format.await;
12632    editor.update_in(cx, |editor, window, cx| {
12633        assert_eq!(
12634            editor.text(cx),
12635            r#"
12636                applied-code-action-1-command
12637                applied-code-action-1-edit
12638                applied-formatting
12639                one
12640                two
12641                three
12642                edited
12643            "#
12644            .unindent()
12645        );
12646
12647        // The manual edit is undone first, because it is the last thing the user did
12648        // (even though the command completed afterwards).
12649        editor.undo(&Default::default(), window, cx);
12650        assert_eq!(
12651            editor.text(cx),
12652            r#"
12653                applied-code-action-1-command
12654                applied-code-action-1-edit
12655                applied-formatting
12656                one
12657                two
12658                three
12659            "#
12660            .unindent()
12661        );
12662
12663        // All the formatting (including the command, which completed after the manual edit)
12664        // is undone together.
12665        editor.undo(&Default::default(), window, cx);
12666        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12667    });
12668}
12669
12670#[gpui::test]
12671async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
12672    init_test(cx, |settings| {
12673        settings.defaults.formatter = Some(FormatterList::Vec(vec![Formatter::LanguageServer(
12674            settings::LanguageServerFormatterSpecifier::Current,
12675        )]))
12676    });
12677
12678    let fs = FakeFs::new(cx.executor());
12679    fs.insert_file(path!("/file.ts"), Default::default()).await;
12680
12681    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12682
12683    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12684    language_registry.add(Arc::new(Language::new(
12685        LanguageConfig {
12686            name: "TypeScript".into(),
12687            matcher: LanguageMatcher {
12688                path_suffixes: vec!["ts".to_string()],
12689                ..Default::default()
12690            },
12691            ..LanguageConfig::default()
12692        },
12693        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12694    )));
12695    update_test_language_settings(cx, |settings| {
12696        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12697    });
12698    let mut fake_servers = language_registry.register_fake_lsp(
12699        "TypeScript",
12700        FakeLspAdapter {
12701            capabilities: lsp::ServerCapabilities {
12702                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12703                ..Default::default()
12704            },
12705            ..Default::default()
12706        },
12707    );
12708
12709    let buffer = project
12710        .update(cx, |project, cx| {
12711            project.open_local_buffer(path!("/file.ts"), cx)
12712        })
12713        .await
12714        .unwrap();
12715
12716    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12717    let (editor, cx) = cx.add_window_view(|window, cx| {
12718        build_editor_with_project(project.clone(), buffer, window, cx)
12719    });
12720    editor.update_in(cx, |editor, window, cx| {
12721        editor.set_text(
12722            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12723            window,
12724            cx,
12725        )
12726    });
12727
12728    cx.executor().start_waiting();
12729    let fake_server = fake_servers.next().await.unwrap();
12730
12731    let format = editor
12732        .update_in(cx, |editor, window, cx| {
12733            editor.perform_code_action_kind(
12734                project.clone(),
12735                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12736                window,
12737                cx,
12738            )
12739        })
12740        .unwrap();
12741    fake_server
12742        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
12743            assert_eq!(
12744                params.text_document.uri,
12745                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12746            );
12747            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12748                lsp::CodeAction {
12749                    title: "Organize Imports".to_string(),
12750                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
12751                    edit: Some(lsp::WorkspaceEdit {
12752                        changes: Some(
12753                            [(
12754                                params.text_document.uri.clone(),
12755                                vec![lsp::TextEdit::new(
12756                                    lsp::Range::new(
12757                                        lsp::Position::new(1, 0),
12758                                        lsp::Position::new(2, 0),
12759                                    ),
12760                                    "".to_string(),
12761                                )],
12762                            )]
12763                            .into_iter()
12764                            .collect(),
12765                        ),
12766                        ..Default::default()
12767                    }),
12768                    ..Default::default()
12769                },
12770            )]))
12771        })
12772        .next()
12773        .await;
12774    cx.executor().start_waiting();
12775    format.await;
12776    assert_eq!(
12777        editor.update(cx, |editor, cx| editor.text(cx)),
12778        "import { a } from 'module';\n\nconst x = a;\n"
12779    );
12780
12781    editor.update_in(cx, |editor, window, cx| {
12782        editor.set_text(
12783            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12784            window,
12785            cx,
12786        )
12787    });
12788    // Ensure we don't lock if code action hangs.
12789    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12790        move |params, _| async move {
12791            assert_eq!(
12792                params.text_document.uri,
12793                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12794            );
12795            futures::future::pending::<()>().await;
12796            unreachable!()
12797        },
12798    );
12799    let format = editor
12800        .update_in(cx, |editor, window, cx| {
12801            editor.perform_code_action_kind(
12802                project,
12803                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12804                window,
12805                cx,
12806            )
12807        })
12808        .unwrap();
12809    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
12810    cx.executor().start_waiting();
12811    format.await;
12812    assert_eq!(
12813        editor.update(cx, |editor, cx| editor.text(cx)),
12814        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
12815    );
12816}
12817
12818#[gpui::test]
12819async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
12820    init_test(cx, |_| {});
12821
12822    let mut cx = EditorLspTestContext::new_rust(
12823        lsp::ServerCapabilities {
12824            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12825            ..Default::default()
12826        },
12827        cx,
12828    )
12829    .await;
12830
12831    cx.set_state(indoc! {"
12832        one.twoˇ
12833    "});
12834
12835    // The format request takes a long time. When it completes, it inserts
12836    // a newline and an indent before the `.`
12837    cx.lsp
12838        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
12839            let executor = cx.background_executor().clone();
12840            async move {
12841                executor.timer(Duration::from_millis(100)).await;
12842                Ok(Some(vec![lsp::TextEdit {
12843                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
12844                    new_text: "\n    ".into(),
12845                }]))
12846            }
12847        });
12848
12849    // Submit a format request.
12850    let format_1 = cx
12851        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12852        .unwrap();
12853    cx.executor().run_until_parked();
12854
12855    // Submit a second format request.
12856    let format_2 = cx
12857        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12858        .unwrap();
12859    cx.executor().run_until_parked();
12860
12861    // Wait for both format requests to complete
12862    cx.executor().advance_clock(Duration::from_millis(200));
12863    cx.executor().start_waiting();
12864    format_1.await.unwrap();
12865    cx.executor().start_waiting();
12866    format_2.await.unwrap();
12867
12868    // The formatting edits only happens once.
12869    cx.assert_editor_state(indoc! {"
12870        one
12871            .twoˇ
12872    "});
12873}
12874
12875#[gpui::test]
12876async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
12877    init_test(cx, |settings| {
12878        settings.defaults.formatter = Some(FormatterList::default())
12879    });
12880
12881    let mut cx = EditorLspTestContext::new_rust(
12882        lsp::ServerCapabilities {
12883            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12884            ..Default::default()
12885        },
12886        cx,
12887    )
12888    .await;
12889
12890    // Record which buffer changes have been sent to the language server
12891    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
12892    cx.lsp
12893        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
12894            let buffer_changes = buffer_changes.clone();
12895            move |params, _| {
12896                buffer_changes.lock().extend(
12897                    params
12898                        .content_changes
12899                        .into_iter()
12900                        .map(|e| (e.range.unwrap(), e.text)),
12901                );
12902            }
12903        });
12904    // Handle formatting requests to the language server.
12905    cx.lsp
12906        .set_request_handler::<lsp::request::Formatting, _, _>({
12907            let buffer_changes = buffer_changes.clone();
12908            move |_, _| {
12909                let buffer_changes = buffer_changes.clone();
12910                // Insert blank lines between each line of the buffer.
12911                async move {
12912                    // When formatting is requested, trailing whitespace has already been stripped,
12913                    // and the trailing newline has already been added.
12914                    assert_eq!(
12915                        &buffer_changes.lock()[1..],
12916                        &[
12917                            (
12918                                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
12919                                "".into()
12920                            ),
12921                            (
12922                                lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
12923                                "".into()
12924                            ),
12925                            (
12926                                lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
12927                                "\n".into()
12928                            ),
12929                        ]
12930                    );
12931
12932                    Ok(Some(vec![
12933                        lsp::TextEdit {
12934                            range: lsp::Range::new(
12935                                lsp::Position::new(1, 0),
12936                                lsp::Position::new(1, 0),
12937                            ),
12938                            new_text: "\n".into(),
12939                        },
12940                        lsp::TextEdit {
12941                            range: lsp::Range::new(
12942                                lsp::Position::new(2, 0),
12943                                lsp::Position::new(2, 0),
12944                            ),
12945                            new_text: "\n".into(),
12946                        },
12947                    ]))
12948                }
12949            }
12950        });
12951
12952    // Set up a buffer white some trailing whitespace and no trailing newline.
12953    cx.set_state(
12954        &[
12955            "one ",   //
12956            "twoˇ",   //
12957            "three ", //
12958            "four",   //
12959        ]
12960        .join("\n"),
12961    );
12962    cx.run_until_parked();
12963
12964    // Submit a format request.
12965    let format = cx
12966        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12967        .unwrap();
12968
12969    cx.run_until_parked();
12970    // After formatting the buffer, the trailing whitespace is stripped,
12971    // a newline is appended, and the edits provided by the language server
12972    // have been applied.
12973    format.await.unwrap();
12974
12975    cx.assert_editor_state(
12976        &[
12977            "one",   //
12978            "",      //
12979            "twoˇ",  //
12980            "",      //
12981            "three", //
12982            "four",  //
12983            "",      //
12984        ]
12985        .join("\n"),
12986    );
12987
12988    // Undoing the formatting undoes the trailing whitespace removal, the
12989    // trailing newline, and the LSP edits.
12990    cx.update_buffer(|buffer, cx| buffer.undo(cx));
12991    cx.assert_editor_state(
12992        &[
12993            "one ",   //
12994            "twoˇ",   //
12995            "three ", //
12996            "four",   //
12997        ]
12998        .join("\n"),
12999    );
13000}
13001
13002#[gpui::test]
13003async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
13004    cx: &mut TestAppContext,
13005) {
13006    init_test(cx, |_| {});
13007
13008    cx.update(|cx| {
13009        cx.update_global::<SettingsStore, _>(|settings, cx| {
13010            settings.update_user_settings(cx, |settings| {
13011                settings.editor.auto_signature_help = Some(true);
13012            });
13013        });
13014    });
13015
13016    let mut cx = EditorLspTestContext::new_rust(
13017        lsp::ServerCapabilities {
13018            signature_help_provider: Some(lsp::SignatureHelpOptions {
13019                ..Default::default()
13020            }),
13021            ..Default::default()
13022        },
13023        cx,
13024    )
13025    .await;
13026
13027    let language = Language::new(
13028        LanguageConfig {
13029            name: "Rust".into(),
13030            brackets: BracketPairConfig {
13031                pairs: vec![
13032                    BracketPair {
13033                        start: "{".to_string(),
13034                        end: "}".to_string(),
13035                        close: true,
13036                        surround: true,
13037                        newline: true,
13038                    },
13039                    BracketPair {
13040                        start: "(".to_string(),
13041                        end: ")".to_string(),
13042                        close: true,
13043                        surround: true,
13044                        newline: true,
13045                    },
13046                    BracketPair {
13047                        start: "/*".to_string(),
13048                        end: " */".to_string(),
13049                        close: true,
13050                        surround: true,
13051                        newline: true,
13052                    },
13053                    BracketPair {
13054                        start: "[".to_string(),
13055                        end: "]".to_string(),
13056                        close: false,
13057                        surround: false,
13058                        newline: true,
13059                    },
13060                    BracketPair {
13061                        start: "\"".to_string(),
13062                        end: "\"".to_string(),
13063                        close: true,
13064                        surround: true,
13065                        newline: false,
13066                    },
13067                    BracketPair {
13068                        start: "<".to_string(),
13069                        end: ">".to_string(),
13070                        close: false,
13071                        surround: true,
13072                        newline: true,
13073                    },
13074                ],
13075                ..Default::default()
13076            },
13077            autoclose_before: "})]".to_string(),
13078            ..Default::default()
13079        },
13080        Some(tree_sitter_rust::LANGUAGE.into()),
13081    );
13082    let language = Arc::new(language);
13083
13084    cx.language_registry().add(language.clone());
13085    cx.update_buffer(|buffer, cx| {
13086        buffer.set_language(Some(language), cx);
13087    });
13088
13089    cx.set_state(
13090        &r#"
13091            fn main() {
13092                sampleˇ
13093            }
13094        "#
13095        .unindent(),
13096    );
13097
13098    cx.update_editor(|editor, window, cx| {
13099        editor.handle_input("(", window, cx);
13100    });
13101    cx.assert_editor_state(
13102        &"
13103            fn main() {
13104                sample(ˇ)
13105            }
13106        "
13107        .unindent(),
13108    );
13109
13110    let mocked_response = lsp::SignatureHelp {
13111        signatures: vec![lsp::SignatureInformation {
13112            label: "fn sample(param1: u8, param2: u8)".to_string(),
13113            documentation: None,
13114            parameters: Some(vec![
13115                lsp::ParameterInformation {
13116                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13117                    documentation: None,
13118                },
13119                lsp::ParameterInformation {
13120                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13121                    documentation: None,
13122                },
13123            ]),
13124            active_parameter: None,
13125        }],
13126        active_signature: Some(0),
13127        active_parameter: Some(0),
13128    };
13129    handle_signature_help_request(&mut cx, mocked_response).await;
13130
13131    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13132        .await;
13133
13134    cx.editor(|editor, _, _| {
13135        let signature_help_state = editor.signature_help_state.popover().cloned();
13136        let signature = signature_help_state.unwrap();
13137        assert_eq!(
13138            signature.signatures[signature.current_signature].label,
13139            "fn sample(param1: u8, param2: u8)"
13140        );
13141    });
13142}
13143
13144#[gpui::test]
13145async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
13146    init_test(cx, |_| {});
13147
13148    cx.update(|cx| {
13149        cx.update_global::<SettingsStore, _>(|settings, cx| {
13150            settings.update_user_settings(cx, |settings| {
13151                settings.editor.auto_signature_help = Some(false);
13152                settings.editor.show_signature_help_after_edits = Some(false);
13153            });
13154        });
13155    });
13156
13157    let mut cx = EditorLspTestContext::new_rust(
13158        lsp::ServerCapabilities {
13159            signature_help_provider: Some(lsp::SignatureHelpOptions {
13160                ..Default::default()
13161            }),
13162            ..Default::default()
13163        },
13164        cx,
13165    )
13166    .await;
13167
13168    let language = Language::new(
13169        LanguageConfig {
13170            name: "Rust".into(),
13171            brackets: BracketPairConfig {
13172                pairs: vec![
13173                    BracketPair {
13174                        start: "{".to_string(),
13175                        end: "}".to_string(),
13176                        close: true,
13177                        surround: true,
13178                        newline: true,
13179                    },
13180                    BracketPair {
13181                        start: "(".to_string(),
13182                        end: ")".to_string(),
13183                        close: true,
13184                        surround: true,
13185                        newline: true,
13186                    },
13187                    BracketPair {
13188                        start: "/*".to_string(),
13189                        end: " */".to_string(),
13190                        close: true,
13191                        surround: true,
13192                        newline: true,
13193                    },
13194                    BracketPair {
13195                        start: "[".to_string(),
13196                        end: "]".to_string(),
13197                        close: false,
13198                        surround: false,
13199                        newline: true,
13200                    },
13201                    BracketPair {
13202                        start: "\"".to_string(),
13203                        end: "\"".to_string(),
13204                        close: true,
13205                        surround: true,
13206                        newline: false,
13207                    },
13208                    BracketPair {
13209                        start: "<".to_string(),
13210                        end: ">".to_string(),
13211                        close: false,
13212                        surround: true,
13213                        newline: true,
13214                    },
13215                ],
13216                ..Default::default()
13217            },
13218            autoclose_before: "})]".to_string(),
13219            ..Default::default()
13220        },
13221        Some(tree_sitter_rust::LANGUAGE.into()),
13222    );
13223    let language = Arc::new(language);
13224
13225    cx.language_registry().add(language.clone());
13226    cx.update_buffer(|buffer, cx| {
13227        buffer.set_language(Some(language), cx);
13228    });
13229
13230    // Ensure that signature_help is not called when no signature help is enabled.
13231    cx.set_state(
13232        &r#"
13233            fn main() {
13234                sampleˇ
13235            }
13236        "#
13237        .unindent(),
13238    );
13239    cx.update_editor(|editor, window, cx| {
13240        editor.handle_input("(", window, cx);
13241    });
13242    cx.assert_editor_state(
13243        &"
13244            fn main() {
13245                sample(ˇ)
13246            }
13247        "
13248        .unindent(),
13249    );
13250    cx.editor(|editor, _, _| {
13251        assert!(editor.signature_help_state.task().is_none());
13252    });
13253
13254    let mocked_response = lsp::SignatureHelp {
13255        signatures: vec![lsp::SignatureInformation {
13256            label: "fn sample(param1: u8, param2: u8)".to_string(),
13257            documentation: None,
13258            parameters: Some(vec![
13259                lsp::ParameterInformation {
13260                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13261                    documentation: None,
13262                },
13263                lsp::ParameterInformation {
13264                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13265                    documentation: None,
13266                },
13267            ]),
13268            active_parameter: None,
13269        }],
13270        active_signature: Some(0),
13271        active_parameter: Some(0),
13272    };
13273
13274    // Ensure that signature_help is called when enabled afte edits
13275    cx.update(|_, cx| {
13276        cx.update_global::<SettingsStore, _>(|settings, cx| {
13277            settings.update_user_settings(cx, |settings| {
13278                settings.editor.auto_signature_help = Some(false);
13279                settings.editor.show_signature_help_after_edits = Some(true);
13280            });
13281        });
13282    });
13283    cx.set_state(
13284        &r#"
13285            fn main() {
13286                sampleˇ
13287            }
13288        "#
13289        .unindent(),
13290    );
13291    cx.update_editor(|editor, window, cx| {
13292        editor.handle_input("(", window, cx);
13293    });
13294    cx.assert_editor_state(
13295        &"
13296            fn main() {
13297                sample(ˇ)
13298            }
13299        "
13300        .unindent(),
13301    );
13302    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13303    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13304        .await;
13305    cx.update_editor(|editor, _, _| {
13306        let signature_help_state = editor.signature_help_state.popover().cloned();
13307        assert!(signature_help_state.is_some());
13308        let signature = signature_help_state.unwrap();
13309        assert_eq!(
13310            signature.signatures[signature.current_signature].label,
13311            "fn sample(param1: u8, param2: u8)"
13312        );
13313        editor.signature_help_state = SignatureHelpState::default();
13314    });
13315
13316    // Ensure that signature_help is called when auto signature help override is enabled
13317    cx.update(|_, cx| {
13318        cx.update_global::<SettingsStore, _>(|settings, cx| {
13319            settings.update_user_settings(cx, |settings| {
13320                settings.editor.auto_signature_help = Some(true);
13321                settings.editor.show_signature_help_after_edits = Some(false);
13322            });
13323        });
13324    });
13325    cx.set_state(
13326        &r#"
13327            fn main() {
13328                sampleˇ
13329            }
13330        "#
13331        .unindent(),
13332    );
13333    cx.update_editor(|editor, window, cx| {
13334        editor.handle_input("(", window, cx);
13335    });
13336    cx.assert_editor_state(
13337        &"
13338            fn main() {
13339                sample(ˇ)
13340            }
13341        "
13342        .unindent(),
13343    );
13344    handle_signature_help_request(&mut cx, mocked_response).await;
13345    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13346        .await;
13347    cx.editor(|editor, _, _| {
13348        let signature_help_state = editor.signature_help_state.popover().cloned();
13349        assert!(signature_help_state.is_some());
13350        let signature = signature_help_state.unwrap();
13351        assert_eq!(
13352            signature.signatures[signature.current_signature].label,
13353            "fn sample(param1: u8, param2: u8)"
13354        );
13355    });
13356}
13357
13358#[gpui::test]
13359async fn test_signature_help(cx: &mut TestAppContext) {
13360    init_test(cx, |_| {});
13361    cx.update(|cx| {
13362        cx.update_global::<SettingsStore, _>(|settings, cx| {
13363            settings.update_user_settings(cx, |settings| {
13364                settings.editor.auto_signature_help = Some(true);
13365            });
13366        });
13367    });
13368
13369    let mut cx = EditorLspTestContext::new_rust(
13370        lsp::ServerCapabilities {
13371            signature_help_provider: Some(lsp::SignatureHelpOptions {
13372                ..Default::default()
13373            }),
13374            ..Default::default()
13375        },
13376        cx,
13377    )
13378    .await;
13379
13380    // A test that directly calls `show_signature_help`
13381    cx.update_editor(|editor, window, cx| {
13382        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13383    });
13384
13385    let mocked_response = lsp::SignatureHelp {
13386        signatures: vec![lsp::SignatureInformation {
13387            label: "fn sample(param1: u8, param2: u8)".to_string(),
13388            documentation: None,
13389            parameters: Some(vec![
13390                lsp::ParameterInformation {
13391                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13392                    documentation: None,
13393                },
13394                lsp::ParameterInformation {
13395                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13396                    documentation: None,
13397                },
13398            ]),
13399            active_parameter: None,
13400        }],
13401        active_signature: Some(0),
13402        active_parameter: Some(0),
13403    };
13404    handle_signature_help_request(&mut cx, mocked_response).await;
13405
13406    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13407        .await;
13408
13409    cx.editor(|editor, _, _| {
13410        let signature_help_state = editor.signature_help_state.popover().cloned();
13411        assert!(signature_help_state.is_some());
13412        let signature = signature_help_state.unwrap();
13413        assert_eq!(
13414            signature.signatures[signature.current_signature].label,
13415            "fn sample(param1: u8, param2: u8)"
13416        );
13417    });
13418
13419    // When exiting outside from inside the brackets, `signature_help` is closed.
13420    cx.set_state(indoc! {"
13421        fn main() {
13422            sample(ˇ);
13423        }
13424
13425        fn sample(param1: u8, param2: u8) {}
13426    "});
13427
13428    cx.update_editor(|editor, window, cx| {
13429        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13430            s.select_ranges([0..0])
13431        });
13432    });
13433
13434    let mocked_response = lsp::SignatureHelp {
13435        signatures: Vec::new(),
13436        active_signature: None,
13437        active_parameter: None,
13438    };
13439    handle_signature_help_request(&mut cx, mocked_response).await;
13440
13441    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13442        .await;
13443
13444    cx.editor(|editor, _, _| {
13445        assert!(!editor.signature_help_state.is_shown());
13446    });
13447
13448    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
13449    cx.set_state(indoc! {"
13450        fn main() {
13451            sample(ˇ);
13452        }
13453
13454        fn sample(param1: u8, param2: u8) {}
13455    "});
13456
13457    let mocked_response = lsp::SignatureHelp {
13458        signatures: vec![lsp::SignatureInformation {
13459            label: "fn sample(param1: u8, param2: u8)".to_string(),
13460            documentation: None,
13461            parameters: Some(vec![
13462                lsp::ParameterInformation {
13463                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13464                    documentation: None,
13465                },
13466                lsp::ParameterInformation {
13467                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13468                    documentation: None,
13469                },
13470            ]),
13471            active_parameter: None,
13472        }],
13473        active_signature: Some(0),
13474        active_parameter: Some(0),
13475    };
13476    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13477    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13478        .await;
13479    cx.editor(|editor, _, _| {
13480        assert!(editor.signature_help_state.is_shown());
13481    });
13482
13483    // Restore the popover with more parameter input
13484    cx.set_state(indoc! {"
13485        fn main() {
13486            sample(param1, param2ˇ);
13487        }
13488
13489        fn sample(param1: u8, param2: u8) {}
13490    "});
13491
13492    let mocked_response = lsp::SignatureHelp {
13493        signatures: vec![lsp::SignatureInformation {
13494            label: "fn sample(param1: u8, param2: u8)".to_string(),
13495            documentation: None,
13496            parameters: Some(vec![
13497                lsp::ParameterInformation {
13498                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13499                    documentation: None,
13500                },
13501                lsp::ParameterInformation {
13502                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13503                    documentation: None,
13504                },
13505            ]),
13506            active_parameter: None,
13507        }],
13508        active_signature: Some(0),
13509        active_parameter: Some(1),
13510    };
13511    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13512    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13513        .await;
13514
13515    // When selecting a range, the popover is gone.
13516    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
13517    cx.update_editor(|editor, window, cx| {
13518        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13519            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13520        })
13521    });
13522    cx.assert_editor_state(indoc! {"
13523        fn main() {
13524            sample(param1, «ˇparam2»);
13525        }
13526
13527        fn sample(param1: u8, param2: u8) {}
13528    "});
13529    cx.editor(|editor, _, _| {
13530        assert!(!editor.signature_help_state.is_shown());
13531    });
13532
13533    // When unselecting again, the popover is back if within the brackets.
13534    cx.update_editor(|editor, window, cx| {
13535        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13536            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13537        })
13538    });
13539    cx.assert_editor_state(indoc! {"
13540        fn main() {
13541            sample(param1, ˇparam2);
13542        }
13543
13544        fn sample(param1: u8, param2: u8) {}
13545    "});
13546    handle_signature_help_request(&mut cx, mocked_response).await;
13547    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13548        .await;
13549    cx.editor(|editor, _, _| {
13550        assert!(editor.signature_help_state.is_shown());
13551    });
13552
13553    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
13554    cx.update_editor(|editor, window, cx| {
13555        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13556            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
13557            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13558        })
13559    });
13560    cx.assert_editor_state(indoc! {"
13561        fn main() {
13562            sample(param1, ˇparam2);
13563        }
13564
13565        fn sample(param1: u8, param2: u8) {}
13566    "});
13567
13568    let mocked_response = lsp::SignatureHelp {
13569        signatures: vec![lsp::SignatureInformation {
13570            label: "fn sample(param1: u8, param2: u8)".to_string(),
13571            documentation: None,
13572            parameters: Some(vec![
13573                lsp::ParameterInformation {
13574                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13575                    documentation: None,
13576                },
13577                lsp::ParameterInformation {
13578                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13579                    documentation: None,
13580                },
13581            ]),
13582            active_parameter: None,
13583        }],
13584        active_signature: Some(0),
13585        active_parameter: Some(1),
13586    };
13587    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13588    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13589        .await;
13590    cx.update_editor(|editor, _, cx| {
13591        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
13592    });
13593    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13594        .await;
13595    cx.update_editor(|editor, window, cx| {
13596        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13597            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13598        })
13599    });
13600    cx.assert_editor_state(indoc! {"
13601        fn main() {
13602            sample(param1, «ˇparam2»);
13603        }
13604
13605        fn sample(param1: u8, param2: u8) {}
13606    "});
13607    cx.update_editor(|editor, window, cx| {
13608        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13609            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13610        })
13611    });
13612    cx.assert_editor_state(indoc! {"
13613        fn main() {
13614            sample(param1, ˇparam2);
13615        }
13616
13617        fn sample(param1: u8, param2: u8) {}
13618    "});
13619    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
13620        .await;
13621}
13622
13623#[gpui::test]
13624async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
13625    init_test(cx, |_| {});
13626
13627    let mut cx = EditorLspTestContext::new_rust(
13628        lsp::ServerCapabilities {
13629            signature_help_provider: Some(lsp::SignatureHelpOptions {
13630                ..Default::default()
13631            }),
13632            ..Default::default()
13633        },
13634        cx,
13635    )
13636    .await;
13637
13638    cx.set_state(indoc! {"
13639        fn main() {
13640            overloadedˇ
13641        }
13642    "});
13643
13644    cx.update_editor(|editor, window, cx| {
13645        editor.handle_input("(", window, cx);
13646        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13647    });
13648
13649    // Mock response with 3 signatures
13650    let mocked_response = lsp::SignatureHelp {
13651        signatures: vec![
13652            lsp::SignatureInformation {
13653                label: "fn overloaded(x: i32)".to_string(),
13654                documentation: None,
13655                parameters: Some(vec![lsp::ParameterInformation {
13656                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13657                    documentation: None,
13658                }]),
13659                active_parameter: None,
13660            },
13661            lsp::SignatureInformation {
13662                label: "fn overloaded(x: i32, y: i32)".to_string(),
13663                documentation: None,
13664                parameters: Some(vec![
13665                    lsp::ParameterInformation {
13666                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13667                        documentation: None,
13668                    },
13669                    lsp::ParameterInformation {
13670                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13671                        documentation: None,
13672                    },
13673                ]),
13674                active_parameter: None,
13675            },
13676            lsp::SignatureInformation {
13677                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
13678                documentation: None,
13679                parameters: Some(vec![
13680                    lsp::ParameterInformation {
13681                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13682                        documentation: None,
13683                    },
13684                    lsp::ParameterInformation {
13685                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13686                        documentation: None,
13687                    },
13688                    lsp::ParameterInformation {
13689                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
13690                        documentation: None,
13691                    },
13692                ]),
13693                active_parameter: None,
13694            },
13695        ],
13696        active_signature: Some(1),
13697        active_parameter: Some(0),
13698    };
13699    handle_signature_help_request(&mut cx, mocked_response).await;
13700
13701    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13702        .await;
13703
13704    // Verify we have multiple signatures and the right one is selected
13705    cx.editor(|editor, _, _| {
13706        let popover = editor.signature_help_state.popover().cloned().unwrap();
13707        assert_eq!(popover.signatures.len(), 3);
13708        // active_signature was 1, so that should be the current
13709        assert_eq!(popover.current_signature, 1);
13710        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
13711        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
13712        assert_eq!(
13713            popover.signatures[2].label,
13714            "fn overloaded(x: i32, y: i32, z: i32)"
13715        );
13716    });
13717
13718    // Test navigation functionality
13719    cx.update_editor(|editor, window, cx| {
13720        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13721    });
13722
13723    cx.editor(|editor, _, _| {
13724        let popover = editor.signature_help_state.popover().cloned().unwrap();
13725        assert_eq!(popover.current_signature, 2);
13726    });
13727
13728    // Test wrap around
13729    cx.update_editor(|editor, window, cx| {
13730        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13731    });
13732
13733    cx.editor(|editor, _, _| {
13734        let popover = editor.signature_help_state.popover().cloned().unwrap();
13735        assert_eq!(popover.current_signature, 0);
13736    });
13737
13738    // Test previous navigation
13739    cx.update_editor(|editor, window, cx| {
13740        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
13741    });
13742
13743    cx.editor(|editor, _, _| {
13744        let popover = editor.signature_help_state.popover().cloned().unwrap();
13745        assert_eq!(popover.current_signature, 2);
13746    });
13747}
13748
13749#[gpui::test]
13750async fn test_completion_mode(cx: &mut TestAppContext) {
13751    init_test(cx, |_| {});
13752    let mut cx = EditorLspTestContext::new_rust(
13753        lsp::ServerCapabilities {
13754            completion_provider: Some(lsp::CompletionOptions {
13755                resolve_provider: Some(true),
13756                ..Default::default()
13757            }),
13758            ..Default::default()
13759        },
13760        cx,
13761    )
13762    .await;
13763
13764    struct Run {
13765        run_description: &'static str,
13766        initial_state: String,
13767        buffer_marked_text: String,
13768        completion_label: &'static str,
13769        completion_text: &'static str,
13770        expected_with_insert_mode: String,
13771        expected_with_replace_mode: String,
13772        expected_with_replace_subsequence_mode: String,
13773        expected_with_replace_suffix_mode: String,
13774    }
13775
13776    let runs = [
13777        Run {
13778            run_description: "Start of word matches completion text",
13779            initial_state: "before ediˇ after".into(),
13780            buffer_marked_text: "before <edi|> after".into(),
13781            completion_label: "editor",
13782            completion_text: "editor",
13783            expected_with_insert_mode: "before editorˇ after".into(),
13784            expected_with_replace_mode: "before editorˇ after".into(),
13785            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13786            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13787        },
13788        Run {
13789            run_description: "Accept same text at the middle of the word",
13790            initial_state: "before ediˇtor after".into(),
13791            buffer_marked_text: "before <edi|tor> after".into(),
13792            completion_label: "editor",
13793            completion_text: "editor",
13794            expected_with_insert_mode: "before editorˇtor after".into(),
13795            expected_with_replace_mode: "before editorˇ after".into(),
13796            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13797            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13798        },
13799        Run {
13800            run_description: "End of word matches completion text -- cursor at end",
13801            initial_state: "before torˇ after".into(),
13802            buffer_marked_text: "before <tor|> after".into(),
13803            completion_label: "editor",
13804            completion_text: "editor",
13805            expected_with_insert_mode: "before editorˇ after".into(),
13806            expected_with_replace_mode: "before editorˇ after".into(),
13807            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13808            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13809        },
13810        Run {
13811            run_description: "End of word matches completion text -- cursor at start",
13812            initial_state: "before ˇtor after".into(),
13813            buffer_marked_text: "before <|tor> after".into(),
13814            completion_label: "editor",
13815            completion_text: "editor",
13816            expected_with_insert_mode: "before editorˇtor after".into(),
13817            expected_with_replace_mode: "before editorˇ after".into(),
13818            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13819            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13820        },
13821        Run {
13822            run_description: "Prepend text containing whitespace",
13823            initial_state: "pˇfield: bool".into(),
13824            buffer_marked_text: "<p|field>: bool".into(),
13825            completion_label: "pub ",
13826            completion_text: "pub ",
13827            expected_with_insert_mode: "pub ˇfield: bool".into(),
13828            expected_with_replace_mode: "pub ˇ: bool".into(),
13829            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
13830            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
13831        },
13832        Run {
13833            run_description: "Add element to start of list",
13834            initial_state: "[element_ˇelement_2]".into(),
13835            buffer_marked_text: "[<element_|element_2>]".into(),
13836            completion_label: "element_1",
13837            completion_text: "element_1",
13838            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
13839            expected_with_replace_mode: "[element_1ˇ]".into(),
13840            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
13841            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
13842        },
13843        Run {
13844            run_description: "Add element to start of list -- first and second elements are equal",
13845            initial_state: "[elˇelement]".into(),
13846            buffer_marked_text: "[<el|element>]".into(),
13847            completion_label: "element",
13848            completion_text: "element",
13849            expected_with_insert_mode: "[elementˇelement]".into(),
13850            expected_with_replace_mode: "[elementˇ]".into(),
13851            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
13852            expected_with_replace_suffix_mode: "[elementˇ]".into(),
13853        },
13854        Run {
13855            run_description: "Ends with matching suffix",
13856            initial_state: "SubˇError".into(),
13857            buffer_marked_text: "<Sub|Error>".into(),
13858            completion_label: "SubscriptionError",
13859            completion_text: "SubscriptionError",
13860            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
13861            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13862            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13863            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
13864        },
13865        Run {
13866            run_description: "Suffix is a subsequence -- contiguous",
13867            initial_state: "SubˇErr".into(),
13868            buffer_marked_text: "<Sub|Err>".into(),
13869            completion_label: "SubscriptionError",
13870            completion_text: "SubscriptionError",
13871            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
13872            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13873            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13874            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
13875        },
13876        Run {
13877            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
13878            initial_state: "Suˇscrirr".into(),
13879            buffer_marked_text: "<Su|scrirr>".into(),
13880            completion_label: "SubscriptionError",
13881            completion_text: "SubscriptionError",
13882            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
13883            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13884            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13885            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
13886        },
13887        Run {
13888            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
13889            initial_state: "foo(indˇix)".into(),
13890            buffer_marked_text: "foo(<ind|ix>)".into(),
13891            completion_label: "node_index",
13892            completion_text: "node_index",
13893            expected_with_insert_mode: "foo(node_indexˇix)".into(),
13894            expected_with_replace_mode: "foo(node_indexˇ)".into(),
13895            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
13896            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
13897        },
13898        Run {
13899            run_description: "Replace range ends before cursor - should extend to cursor",
13900            initial_state: "before editˇo after".into(),
13901            buffer_marked_text: "before <{ed}>it|o after".into(),
13902            completion_label: "editor",
13903            completion_text: "editor",
13904            expected_with_insert_mode: "before editorˇo after".into(),
13905            expected_with_replace_mode: "before editorˇo after".into(),
13906            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
13907            expected_with_replace_suffix_mode: "before editorˇo after".into(),
13908        },
13909        Run {
13910            run_description: "Uses label for suffix matching",
13911            initial_state: "before ediˇtor after".into(),
13912            buffer_marked_text: "before <edi|tor> after".into(),
13913            completion_label: "editor",
13914            completion_text: "editor()",
13915            expected_with_insert_mode: "before editor()ˇtor after".into(),
13916            expected_with_replace_mode: "before editor()ˇ after".into(),
13917            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
13918            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
13919        },
13920        Run {
13921            run_description: "Case insensitive subsequence and suffix matching",
13922            initial_state: "before EDiˇtoR after".into(),
13923            buffer_marked_text: "before <EDi|toR> after".into(),
13924            completion_label: "editor",
13925            completion_text: "editor",
13926            expected_with_insert_mode: "before editorˇtoR after".into(),
13927            expected_with_replace_mode: "before editorˇ after".into(),
13928            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13929            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13930        },
13931    ];
13932
13933    for run in runs {
13934        let run_variations = [
13935            (LspInsertMode::Insert, run.expected_with_insert_mode),
13936            (LspInsertMode::Replace, run.expected_with_replace_mode),
13937            (
13938                LspInsertMode::ReplaceSubsequence,
13939                run.expected_with_replace_subsequence_mode,
13940            ),
13941            (
13942                LspInsertMode::ReplaceSuffix,
13943                run.expected_with_replace_suffix_mode,
13944            ),
13945        ];
13946
13947        for (lsp_insert_mode, expected_text) in run_variations {
13948            eprintln!(
13949                "run = {:?}, mode = {lsp_insert_mode:.?}",
13950                run.run_description,
13951            );
13952
13953            update_test_language_settings(&mut cx, |settings| {
13954                settings.defaults.completions = Some(CompletionSettingsContent {
13955                    lsp_insert_mode: Some(lsp_insert_mode),
13956                    words: Some(WordsCompletionMode::Disabled),
13957                    words_min_length: Some(0),
13958                    ..Default::default()
13959                });
13960            });
13961
13962            cx.set_state(&run.initial_state);
13963            cx.update_editor(|editor, window, cx| {
13964                editor.show_completions(&ShowCompletions, window, cx);
13965            });
13966
13967            let counter = Arc::new(AtomicUsize::new(0));
13968            handle_completion_request_with_insert_and_replace(
13969                &mut cx,
13970                &run.buffer_marked_text,
13971                vec![(run.completion_label, run.completion_text)],
13972                counter.clone(),
13973            )
13974            .await;
13975            cx.condition(|editor, _| editor.context_menu_visible())
13976                .await;
13977            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13978
13979            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13980                editor
13981                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
13982                    .unwrap()
13983            });
13984            cx.assert_editor_state(&expected_text);
13985            handle_resolve_completion_request(&mut cx, None).await;
13986            apply_additional_edits.await.unwrap();
13987        }
13988    }
13989}
13990
13991#[gpui::test]
13992async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
13993    init_test(cx, |_| {});
13994    let mut cx = EditorLspTestContext::new_rust(
13995        lsp::ServerCapabilities {
13996            completion_provider: Some(lsp::CompletionOptions {
13997                resolve_provider: Some(true),
13998                ..Default::default()
13999            }),
14000            ..Default::default()
14001        },
14002        cx,
14003    )
14004    .await;
14005
14006    let initial_state = "SubˇError";
14007    let buffer_marked_text = "<Sub|Error>";
14008    let completion_text = "SubscriptionError";
14009    let expected_with_insert_mode = "SubscriptionErrorˇError";
14010    let expected_with_replace_mode = "SubscriptionErrorˇ";
14011
14012    update_test_language_settings(&mut cx, |settings| {
14013        settings.defaults.completions = Some(CompletionSettingsContent {
14014            words: Some(WordsCompletionMode::Disabled),
14015            words_min_length: Some(0),
14016            // set the opposite here to ensure that the action is overriding the default behavior
14017            lsp_insert_mode: Some(LspInsertMode::Insert),
14018            ..Default::default()
14019        });
14020    });
14021
14022    cx.set_state(initial_state);
14023    cx.update_editor(|editor, window, cx| {
14024        editor.show_completions(&ShowCompletions, window, cx);
14025    });
14026
14027    let counter = Arc::new(AtomicUsize::new(0));
14028    handle_completion_request_with_insert_and_replace(
14029        &mut cx,
14030        buffer_marked_text,
14031        vec![(completion_text, completion_text)],
14032        counter.clone(),
14033    )
14034    .await;
14035    cx.condition(|editor, _| editor.context_menu_visible())
14036        .await;
14037    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14038
14039    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14040        editor
14041            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14042            .unwrap()
14043    });
14044    cx.assert_editor_state(expected_with_replace_mode);
14045    handle_resolve_completion_request(&mut cx, None).await;
14046    apply_additional_edits.await.unwrap();
14047
14048    update_test_language_settings(&mut cx, |settings| {
14049        settings.defaults.completions = Some(CompletionSettingsContent {
14050            words: Some(WordsCompletionMode::Disabled),
14051            words_min_length: Some(0),
14052            // set the opposite here to ensure that the action is overriding the default behavior
14053            lsp_insert_mode: Some(LspInsertMode::Replace),
14054            ..Default::default()
14055        });
14056    });
14057
14058    cx.set_state(initial_state);
14059    cx.update_editor(|editor, window, cx| {
14060        editor.show_completions(&ShowCompletions, window, cx);
14061    });
14062    handle_completion_request_with_insert_and_replace(
14063        &mut cx,
14064        buffer_marked_text,
14065        vec![(completion_text, completion_text)],
14066        counter.clone(),
14067    )
14068    .await;
14069    cx.condition(|editor, _| editor.context_menu_visible())
14070        .await;
14071    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14072
14073    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14074        editor
14075            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
14076            .unwrap()
14077    });
14078    cx.assert_editor_state(expected_with_insert_mode);
14079    handle_resolve_completion_request(&mut cx, None).await;
14080    apply_additional_edits.await.unwrap();
14081}
14082
14083#[gpui::test]
14084async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
14085    init_test(cx, |_| {});
14086    let mut cx = EditorLspTestContext::new_rust(
14087        lsp::ServerCapabilities {
14088            completion_provider: Some(lsp::CompletionOptions {
14089                resolve_provider: Some(true),
14090                ..Default::default()
14091            }),
14092            ..Default::default()
14093        },
14094        cx,
14095    )
14096    .await;
14097
14098    // scenario: surrounding text matches completion text
14099    let completion_text = "to_offset";
14100    let initial_state = indoc! {"
14101        1. buf.to_offˇsuffix
14102        2. buf.to_offˇsuf
14103        3. buf.to_offˇfix
14104        4. buf.to_offˇ
14105        5. into_offˇensive
14106        6. ˇsuffix
14107        7. let ˇ //
14108        8. aaˇzz
14109        9. buf.to_off«zzzzzˇ»suffix
14110        10. buf.«ˇzzzzz»suffix
14111        11. to_off«ˇzzzzz»
14112
14113        buf.to_offˇsuffix  // newest cursor
14114    "};
14115    let completion_marked_buffer = indoc! {"
14116        1. buf.to_offsuffix
14117        2. buf.to_offsuf
14118        3. buf.to_offfix
14119        4. buf.to_off
14120        5. into_offensive
14121        6. suffix
14122        7. let  //
14123        8. aazz
14124        9. buf.to_offzzzzzsuffix
14125        10. buf.zzzzzsuffix
14126        11. to_offzzzzz
14127
14128        buf.<to_off|suffix>  // newest cursor
14129    "};
14130    let expected = indoc! {"
14131        1. buf.to_offsetˇ
14132        2. buf.to_offsetˇsuf
14133        3. buf.to_offsetˇfix
14134        4. buf.to_offsetˇ
14135        5. into_offsetˇensive
14136        6. to_offsetˇsuffix
14137        7. let to_offsetˇ //
14138        8. aato_offsetˇzz
14139        9. buf.to_offsetˇ
14140        10. buf.to_offsetˇsuffix
14141        11. to_offsetˇ
14142
14143        buf.to_offsetˇ  // newest cursor
14144    "};
14145    cx.set_state(initial_state);
14146    cx.update_editor(|editor, window, cx| {
14147        editor.show_completions(&ShowCompletions, window, cx);
14148    });
14149    handle_completion_request_with_insert_and_replace(
14150        &mut cx,
14151        completion_marked_buffer,
14152        vec![(completion_text, completion_text)],
14153        Arc::new(AtomicUsize::new(0)),
14154    )
14155    .await;
14156    cx.condition(|editor, _| editor.context_menu_visible())
14157        .await;
14158    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14159        editor
14160            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14161            .unwrap()
14162    });
14163    cx.assert_editor_state(expected);
14164    handle_resolve_completion_request(&mut cx, None).await;
14165    apply_additional_edits.await.unwrap();
14166
14167    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
14168    let completion_text = "foo_and_bar";
14169    let initial_state = indoc! {"
14170        1. ooanbˇ
14171        2. zooanbˇ
14172        3. ooanbˇz
14173        4. zooanbˇz
14174        5. ooanˇ
14175        6. oanbˇ
14176
14177        ooanbˇ
14178    "};
14179    let completion_marked_buffer = indoc! {"
14180        1. ooanb
14181        2. zooanb
14182        3. ooanbz
14183        4. zooanbz
14184        5. ooan
14185        6. oanb
14186
14187        <ooanb|>
14188    "};
14189    let expected = indoc! {"
14190        1. foo_and_barˇ
14191        2. zfoo_and_barˇ
14192        3. foo_and_barˇz
14193        4. zfoo_and_barˇz
14194        5. ooanfoo_and_barˇ
14195        6. oanbfoo_and_barˇ
14196
14197        foo_and_barˇ
14198    "};
14199    cx.set_state(initial_state);
14200    cx.update_editor(|editor, window, cx| {
14201        editor.show_completions(&ShowCompletions, window, cx);
14202    });
14203    handle_completion_request_with_insert_and_replace(
14204        &mut cx,
14205        completion_marked_buffer,
14206        vec![(completion_text, completion_text)],
14207        Arc::new(AtomicUsize::new(0)),
14208    )
14209    .await;
14210    cx.condition(|editor, _| editor.context_menu_visible())
14211        .await;
14212    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14213        editor
14214            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14215            .unwrap()
14216    });
14217    cx.assert_editor_state(expected);
14218    handle_resolve_completion_request(&mut cx, None).await;
14219    apply_additional_edits.await.unwrap();
14220
14221    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
14222    // (expects the same as if it was inserted at the end)
14223    let completion_text = "foo_and_bar";
14224    let initial_state = indoc! {"
14225        1. ooˇanb
14226        2. zooˇanb
14227        3. ooˇanbz
14228        4. zooˇanbz
14229
14230        ooˇanb
14231    "};
14232    let completion_marked_buffer = indoc! {"
14233        1. ooanb
14234        2. zooanb
14235        3. ooanbz
14236        4. zooanbz
14237
14238        <oo|anb>
14239    "};
14240    let expected = indoc! {"
14241        1. foo_and_barˇ
14242        2. zfoo_and_barˇ
14243        3. foo_and_barˇz
14244        4. zfoo_and_barˇz
14245
14246        foo_and_barˇ
14247    "};
14248    cx.set_state(initial_state);
14249    cx.update_editor(|editor, window, cx| {
14250        editor.show_completions(&ShowCompletions, window, cx);
14251    });
14252    handle_completion_request_with_insert_and_replace(
14253        &mut cx,
14254        completion_marked_buffer,
14255        vec![(completion_text, completion_text)],
14256        Arc::new(AtomicUsize::new(0)),
14257    )
14258    .await;
14259    cx.condition(|editor, _| editor.context_menu_visible())
14260        .await;
14261    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14262        editor
14263            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14264            .unwrap()
14265    });
14266    cx.assert_editor_state(expected);
14267    handle_resolve_completion_request(&mut cx, None).await;
14268    apply_additional_edits.await.unwrap();
14269}
14270
14271// This used to crash
14272#[gpui::test]
14273async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
14274    init_test(cx, |_| {});
14275
14276    let buffer_text = indoc! {"
14277        fn main() {
14278            10.satu;
14279
14280            //
14281            // separate cursors so they open in different excerpts (manually reproducible)
14282            //
14283
14284            10.satu20;
14285        }
14286    "};
14287    let multibuffer_text_with_selections = indoc! {"
14288        fn main() {
14289            10.satuˇ;
14290
14291            //
14292
14293            //
14294
14295            10.satuˇ20;
14296        }
14297    "};
14298    let expected_multibuffer = indoc! {"
14299        fn main() {
14300            10.saturating_sub()ˇ;
14301
14302            //
14303
14304            //
14305
14306            10.saturating_sub()ˇ;
14307        }
14308    "};
14309
14310    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
14311    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
14312
14313    let fs = FakeFs::new(cx.executor());
14314    fs.insert_tree(
14315        path!("/a"),
14316        json!({
14317            "main.rs": buffer_text,
14318        }),
14319    )
14320    .await;
14321
14322    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14323    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14324    language_registry.add(rust_lang());
14325    let mut fake_servers = language_registry.register_fake_lsp(
14326        "Rust",
14327        FakeLspAdapter {
14328            capabilities: lsp::ServerCapabilities {
14329                completion_provider: Some(lsp::CompletionOptions {
14330                    resolve_provider: None,
14331                    ..lsp::CompletionOptions::default()
14332                }),
14333                ..lsp::ServerCapabilities::default()
14334            },
14335            ..FakeLspAdapter::default()
14336        },
14337    );
14338    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14339    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14340    let buffer = project
14341        .update(cx, |project, cx| {
14342            project.open_local_buffer(path!("/a/main.rs"), cx)
14343        })
14344        .await
14345        .unwrap();
14346
14347    let multi_buffer = cx.new(|cx| {
14348        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
14349        multi_buffer.push_excerpts(
14350            buffer.clone(),
14351            [ExcerptRange::new(0..first_excerpt_end)],
14352            cx,
14353        );
14354        multi_buffer.push_excerpts(
14355            buffer.clone(),
14356            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
14357            cx,
14358        );
14359        multi_buffer
14360    });
14361
14362    let editor = workspace
14363        .update(cx, |_, window, cx| {
14364            cx.new(|cx| {
14365                Editor::new(
14366                    EditorMode::Full {
14367                        scale_ui_elements_with_buffer_font_size: false,
14368                        show_active_line_background: false,
14369                        sizing_behavior: SizingBehavior::Default,
14370                    },
14371                    multi_buffer.clone(),
14372                    Some(project.clone()),
14373                    window,
14374                    cx,
14375                )
14376            })
14377        })
14378        .unwrap();
14379
14380    let pane = workspace
14381        .update(cx, |workspace, _, _| workspace.active_pane().clone())
14382        .unwrap();
14383    pane.update_in(cx, |pane, window, cx| {
14384        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
14385    });
14386
14387    let fake_server = fake_servers.next().await.unwrap();
14388
14389    editor.update_in(cx, |editor, window, cx| {
14390        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14391            s.select_ranges([
14392                Point::new(1, 11)..Point::new(1, 11),
14393                Point::new(7, 11)..Point::new(7, 11),
14394            ])
14395        });
14396
14397        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
14398    });
14399
14400    editor.update_in(cx, |editor, window, cx| {
14401        editor.show_completions(&ShowCompletions, window, cx);
14402    });
14403
14404    fake_server
14405        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14406            let completion_item = lsp::CompletionItem {
14407                label: "saturating_sub()".into(),
14408                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
14409                    lsp::InsertReplaceEdit {
14410                        new_text: "saturating_sub()".to_owned(),
14411                        insert: lsp::Range::new(
14412                            lsp::Position::new(7, 7),
14413                            lsp::Position::new(7, 11),
14414                        ),
14415                        replace: lsp::Range::new(
14416                            lsp::Position::new(7, 7),
14417                            lsp::Position::new(7, 13),
14418                        ),
14419                    },
14420                )),
14421                ..lsp::CompletionItem::default()
14422            };
14423
14424            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
14425        })
14426        .next()
14427        .await
14428        .unwrap();
14429
14430    cx.condition(&editor, |editor, _| editor.context_menu_visible())
14431        .await;
14432
14433    editor
14434        .update_in(cx, |editor, window, cx| {
14435            editor
14436                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14437                .unwrap()
14438        })
14439        .await
14440        .unwrap();
14441
14442    editor.update(cx, |editor, cx| {
14443        assert_text_with_selections(editor, expected_multibuffer, cx);
14444    })
14445}
14446
14447#[gpui::test]
14448async fn test_completion(cx: &mut TestAppContext) {
14449    init_test(cx, |_| {});
14450
14451    let mut cx = EditorLspTestContext::new_rust(
14452        lsp::ServerCapabilities {
14453            completion_provider: Some(lsp::CompletionOptions {
14454                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14455                resolve_provider: Some(true),
14456                ..Default::default()
14457            }),
14458            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14459            ..Default::default()
14460        },
14461        cx,
14462    )
14463    .await;
14464    let counter = Arc::new(AtomicUsize::new(0));
14465
14466    cx.set_state(indoc! {"
14467        oneˇ
14468        two
14469        three
14470    "});
14471    cx.simulate_keystroke(".");
14472    handle_completion_request(
14473        indoc! {"
14474            one.|<>
14475            two
14476            three
14477        "},
14478        vec!["first_completion", "second_completion"],
14479        true,
14480        counter.clone(),
14481        &mut cx,
14482    )
14483    .await;
14484    cx.condition(|editor, _| editor.context_menu_visible())
14485        .await;
14486    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14487
14488    let _handler = handle_signature_help_request(
14489        &mut cx,
14490        lsp::SignatureHelp {
14491            signatures: vec![lsp::SignatureInformation {
14492                label: "test signature".to_string(),
14493                documentation: None,
14494                parameters: Some(vec![lsp::ParameterInformation {
14495                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
14496                    documentation: None,
14497                }]),
14498                active_parameter: None,
14499            }],
14500            active_signature: None,
14501            active_parameter: None,
14502        },
14503    );
14504    cx.update_editor(|editor, window, cx| {
14505        assert!(
14506            !editor.signature_help_state.is_shown(),
14507            "No signature help was called for"
14508        );
14509        editor.show_signature_help(&ShowSignatureHelp, window, cx);
14510    });
14511    cx.run_until_parked();
14512    cx.update_editor(|editor, _, _| {
14513        assert!(
14514            !editor.signature_help_state.is_shown(),
14515            "No signature help should be shown when completions menu is open"
14516        );
14517    });
14518
14519    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14520        editor.context_menu_next(&Default::default(), window, cx);
14521        editor
14522            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14523            .unwrap()
14524    });
14525    cx.assert_editor_state(indoc! {"
14526        one.second_completionˇ
14527        two
14528        three
14529    "});
14530
14531    handle_resolve_completion_request(
14532        &mut cx,
14533        Some(vec![
14534            (
14535                //This overlaps with the primary completion edit which is
14536                //misbehavior from the LSP spec, test that we filter it out
14537                indoc! {"
14538                    one.second_ˇcompletion
14539                    two
14540                    threeˇ
14541                "},
14542                "overlapping additional edit",
14543            ),
14544            (
14545                indoc! {"
14546                    one.second_completion
14547                    two
14548                    threeˇ
14549                "},
14550                "\nadditional edit",
14551            ),
14552        ]),
14553    )
14554    .await;
14555    apply_additional_edits.await.unwrap();
14556    cx.assert_editor_state(indoc! {"
14557        one.second_completionˇ
14558        two
14559        three
14560        additional edit
14561    "});
14562
14563    cx.set_state(indoc! {"
14564        one.second_completion
14565        twoˇ
14566        threeˇ
14567        additional edit
14568    "});
14569    cx.simulate_keystroke(" ");
14570    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14571    cx.simulate_keystroke("s");
14572    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14573
14574    cx.assert_editor_state(indoc! {"
14575        one.second_completion
14576        two sˇ
14577        three sˇ
14578        additional edit
14579    "});
14580    handle_completion_request(
14581        indoc! {"
14582            one.second_completion
14583            two s
14584            three <s|>
14585            additional edit
14586        "},
14587        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14588        true,
14589        counter.clone(),
14590        &mut cx,
14591    )
14592    .await;
14593    cx.condition(|editor, _| editor.context_menu_visible())
14594        .await;
14595    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14596
14597    cx.simulate_keystroke("i");
14598
14599    handle_completion_request(
14600        indoc! {"
14601            one.second_completion
14602            two si
14603            three <si|>
14604            additional edit
14605        "},
14606        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14607        true,
14608        counter.clone(),
14609        &mut cx,
14610    )
14611    .await;
14612    cx.condition(|editor, _| editor.context_menu_visible())
14613        .await;
14614    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14615
14616    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14617        editor
14618            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14619            .unwrap()
14620    });
14621    cx.assert_editor_state(indoc! {"
14622        one.second_completion
14623        two sixth_completionˇ
14624        three sixth_completionˇ
14625        additional edit
14626    "});
14627
14628    apply_additional_edits.await.unwrap();
14629
14630    update_test_language_settings(&mut cx, |settings| {
14631        settings.defaults.show_completions_on_input = Some(false);
14632    });
14633    cx.set_state("editorˇ");
14634    cx.simulate_keystroke(".");
14635    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14636    cx.simulate_keystrokes("c l o");
14637    cx.assert_editor_state("editor.cloˇ");
14638    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14639    cx.update_editor(|editor, window, cx| {
14640        editor.show_completions(&ShowCompletions, window, cx);
14641    });
14642    handle_completion_request(
14643        "editor.<clo|>",
14644        vec!["close", "clobber"],
14645        true,
14646        counter.clone(),
14647        &mut cx,
14648    )
14649    .await;
14650    cx.condition(|editor, _| editor.context_menu_visible())
14651        .await;
14652    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
14653
14654    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14655        editor
14656            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14657            .unwrap()
14658    });
14659    cx.assert_editor_state("editor.clobberˇ");
14660    handle_resolve_completion_request(&mut cx, None).await;
14661    apply_additional_edits.await.unwrap();
14662}
14663
14664#[gpui::test]
14665async fn test_completion_reuse(cx: &mut TestAppContext) {
14666    init_test(cx, |_| {});
14667
14668    let mut cx = EditorLspTestContext::new_rust(
14669        lsp::ServerCapabilities {
14670            completion_provider: Some(lsp::CompletionOptions {
14671                trigger_characters: Some(vec![".".to_string()]),
14672                ..Default::default()
14673            }),
14674            ..Default::default()
14675        },
14676        cx,
14677    )
14678    .await;
14679
14680    let counter = Arc::new(AtomicUsize::new(0));
14681    cx.set_state("objˇ");
14682    cx.simulate_keystroke(".");
14683
14684    // Initial completion request returns complete results
14685    let is_incomplete = false;
14686    handle_completion_request(
14687        "obj.|<>",
14688        vec!["a", "ab", "abc"],
14689        is_incomplete,
14690        counter.clone(),
14691        &mut cx,
14692    )
14693    .await;
14694    cx.run_until_parked();
14695    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14696    cx.assert_editor_state("obj.ˇ");
14697    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14698
14699    // Type "a" - filters existing completions
14700    cx.simulate_keystroke("a");
14701    cx.run_until_parked();
14702    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14703    cx.assert_editor_state("obj.aˇ");
14704    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14705
14706    // Type "b" - filters existing completions
14707    cx.simulate_keystroke("b");
14708    cx.run_until_parked();
14709    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14710    cx.assert_editor_state("obj.abˇ");
14711    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14712
14713    // Type "c" - filters existing completions
14714    cx.simulate_keystroke("c");
14715    cx.run_until_parked();
14716    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14717    cx.assert_editor_state("obj.abcˇ");
14718    check_displayed_completions(vec!["abc"], &mut cx);
14719
14720    // Backspace to delete "c" - filters existing completions
14721    cx.update_editor(|editor, window, cx| {
14722        editor.backspace(&Backspace, window, cx);
14723    });
14724    cx.run_until_parked();
14725    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14726    cx.assert_editor_state("obj.abˇ");
14727    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14728
14729    // Moving cursor to the left dismisses menu.
14730    cx.update_editor(|editor, window, cx| {
14731        editor.move_left(&MoveLeft, window, cx);
14732    });
14733    cx.run_until_parked();
14734    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14735    cx.assert_editor_state("obj.aˇb");
14736    cx.update_editor(|editor, _, _| {
14737        assert_eq!(editor.context_menu_visible(), false);
14738    });
14739
14740    // Type "b" - new request
14741    cx.simulate_keystroke("b");
14742    let is_incomplete = false;
14743    handle_completion_request(
14744        "obj.<ab|>a",
14745        vec!["ab", "abc"],
14746        is_incomplete,
14747        counter.clone(),
14748        &mut cx,
14749    )
14750    .await;
14751    cx.run_until_parked();
14752    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14753    cx.assert_editor_state("obj.abˇb");
14754    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14755
14756    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
14757    cx.update_editor(|editor, window, cx| {
14758        editor.backspace(&Backspace, window, cx);
14759    });
14760    let is_incomplete = false;
14761    handle_completion_request(
14762        "obj.<a|>b",
14763        vec!["a", "ab", "abc"],
14764        is_incomplete,
14765        counter.clone(),
14766        &mut cx,
14767    )
14768    .await;
14769    cx.run_until_parked();
14770    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14771    cx.assert_editor_state("obj.aˇb");
14772    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14773
14774    // Backspace to delete "a" - dismisses menu.
14775    cx.update_editor(|editor, window, cx| {
14776        editor.backspace(&Backspace, window, cx);
14777    });
14778    cx.run_until_parked();
14779    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14780    cx.assert_editor_state("obj.ˇb");
14781    cx.update_editor(|editor, _, _| {
14782        assert_eq!(editor.context_menu_visible(), false);
14783    });
14784}
14785
14786#[gpui::test]
14787async fn test_word_completion(cx: &mut TestAppContext) {
14788    let lsp_fetch_timeout_ms = 10;
14789    init_test(cx, |language_settings| {
14790        language_settings.defaults.completions = Some(CompletionSettingsContent {
14791            words_min_length: Some(0),
14792            lsp_fetch_timeout_ms: Some(10),
14793            lsp_insert_mode: Some(LspInsertMode::Insert),
14794            ..Default::default()
14795        });
14796    });
14797
14798    let mut cx = EditorLspTestContext::new_rust(
14799        lsp::ServerCapabilities {
14800            completion_provider: Some(lsp::CompletionOptions {
14801                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14802                ..lsp::CompletionOptions::default()
14803            }),
14804            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14805            ..lsp::ServerCapabilities::default()
14806        },
14807        cx,
14808    )
14809    .await;
14810
14811    let throttle_completions = Arc::new(AtomicBool::new(false));
14812
14813    let lsp_throttle_completions = throttle_completions.clone();
14814    let _completion_requests_handler =
14815        cx.lsp
14816            .server
14817            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
14818                let lsp_throttle_completions = lsp_throttle_completions.clone();
14819                let cx = cx.clone();
14820                async move {
14821                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
14822                        cx.background_executor()
14823                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
14824                            .await;
14825                    }
14826                    Ok(Some(lsp::CompletionResponse::Array(vec![
14827                        lsp::CompletionItem {
14828                            label: "first".into(),
14829                            ..lsp::CompletionItem::default()
14830                        },
14831                        lsp::CompletionItem {
14832                            label: "last".into(),
14833                            ..lsp::CompletionItem::default()
14834                        },
14835                    ])))
14836                }
14837            });
14838
14839    cx.set_state(indoc! {"
14840        oneˇ
14841        two
14842        three
14843    "});
14844    cx.simulate_keystroke(".");
14845    cx.executor().run_until_parked();
14846    cx.condition(|editor, _| editor.context_menu_visible())
14847        .await;
14848    cx.update_editor(|editor, window, cx| {
14849        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14850        {
14851            assert_eq!(
14852                completion_menu_entries(menu),
14853                &["first", "last"],
14854                "When LSP server is fast to reply, no fallback word completions are used"
14855            );
14856        } else {
14857            panic!("expected completion menu to be open");
14858        }
14859        editor.cancel(&Cancel, window, cx);
14860    });
14861    cx.executor().run_until_parked();
14862    cx.condition(|editor, _| !editor.context_menu_visible())
14863        .await;
14864
14865    throttle_completions.store(true, atomic::Ordering::Release);
14866    cx.simulate_keystroke(".");
14867    cx.executor()
14868        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
14869    cx.executor().run_until_parked();
14870    cx.condition(|editor, _| editor.context_menu_visible())
14871        .await;
14872    cx.update_editor(|editor, _, _| {
14873        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14874        {
14875            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
14876                "When LSP server is slow, document words can be shown instead, if configured accordingly");
14877        } else {
14878            panic!("expected completion menu to be open");
14879        }
14880    });
14881}
14882
14883#[gpui::test]
14884async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
14885    init_test(cx, |language_settings| {
14886        language_settings.defaults.completions = Some(CompletionSettingsContent {
14887            words: Some(WordsCompletionMode::Enabled),
14888            words_min_length: Some(0),
14889            lsp_insert_mode: Some(LspInsertMode::Insert),
14890            ..Default::default()
14891        });
14892    });
14893
14894    let mut cx = EditorLspTestContext::new_rust(
14895        lsp::ServerCapabilities {
14896            completion_provider: Some(lsp::CompletionOptions {
14897                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14898                ..lsp::CompletionOptions::default()
14899            }),
14900            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14901            ..lsp::ServerCapabilities::default()
14902        },
14903        cx,
14904    )
14905    .await;
14906
14907    let _completion_requests_handler =
14908        cx.lsp
14909            .server
14910            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14911                Ok(Some(lsp::CompletionResponse::Array(vec![
14912                    lsp::CompletionItem {
14913                        label: "first".into(),
14914                        ..lsp::CompletionItem::default()
14915                    },
14916                    lsp::CompletionItem {
14917                        label: "last".into(),
14918                        ..lsp::CompletionItem::default()
14919                    },
14920                ])))
14921            });
14922
14923    cx.set_state(indoc! {"ˇ
14924        first
14925        last
14926        second
14927    "});
14928    cx.simulate_keystroke(".");
14929    cx.executor().run_until_parked();
14930    cx.condition(|editor, _| editor.context_menu_visible())
14931        .await;
14932    cx.update_editor(|editor, _, _| {
14933        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14934        {
14935            assert_eq!(
14936                completion_menu_entries(menu),
14937                &["first", "last", "second"],
14938                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
14939            );
14940        } else {
14941            panic!("expected completion menu to be open");
14942        }
14943    });
14944}
14945
14946#[gpui::test]
14947async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
14948    init_test(cx, |language_settings| {
14949        language_settings.defaults.completions = Some(CompletionSettingsContent {
14950            words: Some(WordsCompletionMode::Disabled),
14951            words_min_length: Some(0),
14952            lsp_insert_mode: Some(LspInsertMode::Insert),
14953            ..Default::default()
14954        });
14955    });
14956
14957    let mut cx = EditorLspTestContext::new_rust(
14958        lsp::ServerCapabilities {
14959            completion_provider: Some(lsp::CompletionOptions {
14960                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14961                ..lsp::CompletionOptions::default()
14962            }),
14963            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14964            ..lsp::ServerCapabilities::default()
14965        },
14966        cx,
14967    )
14968    .await;
14969
14970    let _completion_requests_handler =
14971        cx.lsp
14972            .server
14973            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14974                panic!("LSP completions should not be queried when dealing with word completions")
14975            });
14976
14977    cx.set_state(indoc! {"ˇ
14978        first
14979        last
14980        second
14981    "});
14982    cx.update_editor(|editor, window, cx| {
14983        editor.show_word_completions(&ShowWordCompletions, window, cx);
14984    });
14985    cx.executor().run_until_parked();
14986    cx.condition(|editor, _| editor.context_menu_visible())
14987        .await;
14988    cx.update_editor(|editor, _, _| {
14989        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14990        {
14991            assert_eq!(
14992                completion_menu_entries(menu),
14993                &["first", "last", "second"],
14994                "`ShowWordCompletions` action should show word completions"
14995            );
14996        } else {
14997            panic!("expected completion menu to be open");
14998        }
14999    });
15000
15001    cx.simulate_keystroke("l");
15002    cx.executor().run_until_parked();
15003    cx.condition(|editor, _| editor.context_menu_visible())
15004        .await;
15005    cx.update_editor(|editor, _, _| {
15006        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15007        {
15008            assert_eq!(
15009                completion_menu_entries(menu),
15010                &["last"],
15011                "After showing word completions, further editing should filter them and not query the LSP"
15012            );
15013        } else {
15014            panic!("expected completion menu to be open");
15015        }
15016    });
15017}
15018
15019#[gpui::test]
15020async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
15021    init_test(cx, |language_settings| {
15022        language_settings.defaults.completions = Some(CompletionSettingsContent {
15023            words_min_length: Some(0),
15024            lsp: Some(false),
15025            lsp_insert_mode: Some(LspInsertMode::Insert),
15026            ..Default::default()
15027        });
15028    });
15029
15030    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15031
15032    cx.set_state(indoc! {"ˇ
15033        0_usize
15034        let
15035        33
15036        4.5f32
15037    "});
15038    cx.update_editor(|editor, window, cx| {
15039        editor.show_completions(&ShowCompletions, window, cx);
15040    });
15041    cx.executor().run_until_parked();
15042    cx.condition(|editor, _| editor.context_menu_visible())
15043        .await;
15044    cx.update_editor(|editor, window, cx| {
15045        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15046        {
15047            assert_eq!(
15048                completion_menu_entries(menu),
15049                &["let"],
15050                "With no digits in the completion query, no digits should be in the word completions"
15051            );
15052        } else {
15053            panic!("expected completion menu to be open");
15054        }
15055        editor.cancel(&Cancel, window, cx);
15056    });
15057
15058    cx.set_state(indoc! {"15059        0_usize
15060        let
15061        3
15062        33.35f32
15063    "});
15064    cx.update_editor(|editor, window, cx| {
15065        editor.show_completions(&ShowCompletions, window, cx);
15066    });
15067    cx.executor().run_until_parked();
15068    cx.condition(|editor, _| editor.context_menu_visible())
15069        .await;
15070    cx.update_editor(|editor, _, _| {
15071        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15072        {
15073            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
15074                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
15075        } else {
15076            panic!("expected completion menu to be open");
15077        }
15078    });
15079}
15080
15081#[gpui::test]
15082async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
15083    init_test(cx, |language_settings| {
15084        language_settings.defaults.completions = Some(CompletionSettingsContent {
15085            words: Some(WordsCompletionMode::Enabled),
15086            words_min_length: Some(3),
15087            lsp_insert_mode: Some(LspInsertMode::Insert),
15088            ..Default::default()
15089        });
15090    });
15091
15092    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15093    cx.set_state(indoc! {"ˇ
15094        wow
15095        wowen
15096        wowser
15097    "});
15098    cx.simulate_keystroke("w");
15099    cx.executor().run_until_parked();
15100    cx.update_editor(|editor, _, _| {
15101        if editor.context_menu.borrow_mut().is_some() {
15102            panic!(
15103                "expected completion menu to be hidden, as words completion threshold is not met"
15104            );
15105        }
15106    });
15107
15108    cx.update_editor(|editor, window, cx| {
15109        editor.show_word_completions(&ShowWordCompletions, window, cx);
15110    });
15111    cx.executor().run_until_parked();
15112    cx.update_editor(|editor, window, cx| {
15113        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15114        {
15115            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");
15116        } else {
15117            panic!("expected completion menu to be open after the word completions are called with an action");
15118        }
15119
15120        editor.cancel(&Cancel, window, cx);
15121    });
15122    cx.update_editor(|editor, _, _| {
15123        if editor.context_menu.borrow_mut().is_some() {
15124            panic!("expected completion menu to be hidden after canceling");
15125        }
15126    });
15127
15128    cx.simulate_keystroke("o");
15129    cx.executor().run_until_parked();
15130    cx.update_editor(|editor, _, _| {
15131        if editor.context_menu.borrow_mut().is_some() {
15132            panic!(
15133                "expected completion menu to be hidden, as words completion threshold is not met still"
15134            );
15135        }
15136    });
15137
15138    cx.simulate_keystroke("w");
15139    cx.executor().run_until_parked();
15140    cx.update_editor(|editor, _, _| {
15141        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15142        {
15143            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
15144        } else {
15145            panic!("expected completion menu to be open after the word completions threshold is met");
15146        }
15147    });
15148}
15149
15150#[gpui::test]
15151async fn test_word_completions_disabled(cx: &mut TestAppContext) {
15152    init_test(cx, |language_settings| {
15153        language_settings.defaults.completions = Some(CompletionSettingsContent {
15154            words: Some(WordsCompletionMode::Enabled),
15155            words_min_length: Some(0),
15156            lsp_insert_mode: Some(LspInsertMode::Insert),
15157            ..Default::default()
15158        });
15159    });
15160
15161    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15162    cx.update_editor(|editor, _, _| {
15163        editor.disable_word_completions();
15164    });
15165    cx.set_state(indoc! {"ˇ
15166        wow
15167        wowen
15168        wowser
15169    "});
15170    cx.simulate_keystroke("w");
15171    cx.executor().run_until_parked();
15172    cx.update_editor(|editor, _, _| {
15173        if editor.context_menu.borrow_mut().is_some() {
15174            panic!(
15175                "expected completion menu to be hidden, as words completion are disabled for this editor"
15176            );
15177        }
15178    });
15179
15180    cx.update_editor(|editor, window, cx| {
15181        editor.show_word_completions(&ShowWordCompletions, window, cx);
15182    });
15183    cx.executor().run_until_parked();
15184    cx.update_editor(|editor, _, _| {
15185        if editor.context_menu.borrow_mut().is_some() {
15186            panic!(
15187                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
15188            );
15189        }
15190    });
15191}
15192
15193#[gpui::test]
15194async fn test_word_completions_disabled_with_no_provider(cx: &mut TestAppContext) {
15195    init_test(cx, |language_settings| {
15196        language_settings.defaults.completions = Some(CompletionSettingsContent {
15197            words: Some(WordsCompletionMode::Disabled),
15198            words_min_length: Some(0),
15199            lsp_insert_mode: Some(LspInsertMode::Insert),
15200            ..Default::default()
15201        });
15202    });
15203
15204    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15205    cx.update_editor(|editor, _, _| {
15206        editor.set_completion_provider(None);
15207    });
15208    cx.set_state(indoc! {"ˇ
15209        wow
15210        wowen
15211        wowser
15212    "});
15213    cx.simulate_keystroke("w");
15214    cx.executor().run_until_parked();
15215    cx.update_editor(|editor, _, _| {
15216        if editor.context_menu.borrow_mut().is_some() {
15217            panic!("expected completion menu to be hidden, as disabled in settings");
15218        }
15219    });
15220}
15221
15222fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
15223    let position = || lsp::Position {
15224        line: params.text_document_position.position.line,
15225        character: params.text_document_position.position.character,
15226    };
15227    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15228        range: lsp::Range {
15229            start: position(),
15230            end: position(),
15231        },
15232        new_text: text.to_string(),
15233    }))
15234}
15235
15236#[gpui::test]
15237async fn test_multiline_completion(cx: &mut TestAppContext) {
15238    init_test(cx, |_| {});
15239
15240    let fs = FakeFs::new(cx.executor());
15241    fs.insert_tree(
15242        path!("/a"),
15243        json!({
15244            "main.ts": "a",
15245        }),
15246    )
15247    .await;
15248
15249    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15250    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15251    let typescript_language = Arc::new(Language::new(
15252        LanguageConfig {
15253            name: "TypeScript".into(),
15254            matcher: LanguageMatcher {
15255                path_suffixes: vec!["ts".to_string()],
15256                ..LanguageMatcher::default()
15257            },
15258            line_comments: vec!["// ".into()],
15259            ..LanguageConfig::default()
15260        },
15261        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
15262    ));
15263    language_registry.add(typescript_language.clone());
15264    let mut fake_servers = language_registry.register_fake_lsp(
15265        "TypeScript",
15266        FakeLspAdapter {
15267            capabilities: lsp::ServerCapabilities {
15268                completion_provider: Some(lsp::CompletionOptions {
15269                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15270                    ..lsp::CompletionOptions::default()
15271                }),
15272                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15273                ..lsp::ServerCapabilities::default()
15274            },
15275            // Emulate vtsls label generation
15276            label_for_completion: Some(Box::new(|item, _| {
15277                let text = if let Some(description) = item
15278                    .label_details
15279                    .as_ref()
15280                    .and_then(|label_details| label_details.description.as_ref())
15281                {
15282                    format!("{} {}", item.label, description)
15283                } else if let Some(detail) = &item.detail {
15284                    format!("{} {}", item.label, detail)
15285                } else {
15286                    item.label.clone()
15287                };
15288                Some(language::CodeLabel::plain(text, None))
15289            })),
15290            ..FakeLspAdapter::default()
15291        },
15292    );
15293    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15294    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15295    let worktree_id = workspace
15296        .update(cx, |workspace, _window, cx| {
15297            workspace.project().update(cx, |project, cx| {
15298                project.worktrees(cx).next().unwrap().read(cx).id()
15299            })
15300        })
15301        .unwrap();
15302    let _buffer = project
15303        .update(cx, |project, cx| {
15304            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
15305        })
15306        .await
15307        .unwrap();
15308    let editor = workspace
15309        .update(cx, |workspace, window, cx| {
15310            workspace.open_path((worktree_id, rel_path("main.ts")), None, true, window, cx)
15311        })
15312        .unwrap()
15313        .await
15314        .unwrap()
15315        .downcast::<Editor>()
15316        .unwrap();
15317    let fake_server = fake_servers.next().await.unwrap();
15318
15319    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
15320    let multiline_label_2 = "a\nb\nc\n";
15321    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
15322    let multiline_description = "d\ne\nf\n";
15323    let multiline_detail_2 = "g\nh\ni\n";
15324
15325    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
15326        move |params, _| async move {
15327            Ok(Some(lsp::CompletionResponse::Array(vec![
15328                lsp::CompletionItem {
15329                    label: multiline_label.to_string(),
15330                    text_edit: gen_text_edit(&params, "new_text_1"),
15331                    ..lsp::CompletionItem::default()
15332                },
15333                lsp::CompletionItem {
15334                    label: "single line label 1".to_string(),
15335                    detail: Some(multiline_detail.to_string()),
15336                    text_edit: gen_text_edit(&params, "new_text_2"),
15337                    ..lsp::CompletionItem::default()
15338                },
15339                lsp::CompletionItem {
15340                    label: "single line label 2".to_string(),
15341                    label_details: Some(lsp::CompletionItemLabelDetails {
15342                        description: Some(multiline_description.to_string()),
15343                        detail: None,
15344                    }),
15345                    text_edit: gen_text_edit(&params, "new_text_2"),
15346                    ..lsp::CompletionItem::default()
15347                },
15348                lsp::CompletionItem {
15349                    label: multiline_label_2.to_string(),
15350                    detail: Some(multiline_detail_2.to_string()),
15351                    text_edit: gen_text_edit(&params, "new_text_3"),
15352                    ..lsp::CompletionItem::default()
15353                },
15354                lsp::CompletionItem {
15355                    label: "Label with many     spaces and \t but without newlines".to_string(),
15356                    detail: Some(
15357                        "Details with many     spaces and \t but without newlines".to_string(),
15358                    ),
15359                    text_edit: gen_text_edit(&params, "new_text_4"),
15360                    ..lsp::CompletionItem::default()
15361                },
15362            ])))
15363        },
15364    );
15365
15366    editor.update_in(cx, |editor, window, cx| {
15367        cx.focus_self(window);
15368        editor.move_to_end(&MoveToEnd, window, cx);
15369        editor.handle_input(".", window, cx);
15370    });
15371    cx.run_until_parked();
15372    completion_handle.next().await.unwrap();
15373
15374    editor.update(cx, |editor, _| {
15375        assert!(editor.context_menu_visible());
15376        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15377        {
15378            let completion_labels = menu
15379                .completions
15380                .borrow()
15381                .iter()
15382                .map(|c| c.label.text.clone())
15383                .collect::<Vec<_>>();
15384            assert_eq!(
15385                completion_labels,
15386                &[
15387                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
15388                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
15389                    "single line label 2 d e f ",
15390                    "a b c g h i ",
15391                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
15392                ],
15393                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
15394            );
15395
15396            for completion in menu
15397                .completions
15398                .borrow()
15399                .iter() {
15400                    assert_eq!(
15401                        completion.label.filter_range,
15402                        0..completion.label.text.len(),
15403                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
15404                    );
15405                }
15406        } else {
15407            panic!("expected completion menu to be open");
15408        }
15409    });
15410}
15411
15412#[gpui::test]
15413async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
15414    init_test(cx, |_| {});
15415    let mut cx = EditorLspTestContext::new_rust(
15416        lsp::ServerCapabilities {
15417            completion_provider: Some(lsp::CompletionOptions {
15418                trigger_characters: Some(vec![".".to_string()]),
15419                ..Default::default()
15420            }),
15421            ..Default::default()
15422        },
15423        cx,
15424    )
15425    .await;
15426    cx.lsp
15427        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15428            Ok(Some(lsp::CompletionResponse::Array(vec![
15429                lsp::CompletionItem {
15430                    label: "first".into(),
15431                    ..Default::default()
15432                },
15433                lsp::CompletionItem {
15434                    label: "last".into(),
15435                    ..Default::default()
15436                },
15437            ])))
15438        });
15439    cx.set_state("variableˇ");
15440    cx.simulate_keystroke(".");
15441    cx.executor().run_until_parked();
15442
15443    cx.update_editor(|editor, _, _| {
15444        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15445        {
15446            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
15447        } else {
15448            panic!("expected completion menu to be open");
15449        }
15450    });
15451
15452    cx.update_editor(|editor, window, cx| {
15453        editor.move_page_down(&MovePageDown::default(), window, cx);
15454        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15455        {
15456            assert!(
15457                menu.selected_item == 1,
15458                "expected PageDown to select the last item from the context menu"
15459            );
15460        } else {
15461            panic!("expected completion menu to stay open after PageDown");
15462        }
15463    });
15464
15465    cx.update_editor(|editor, window, cx| {
15466        editor.move_page_up(&MovePageUp::default(), window, cx);
15467        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15468        {
15469            assert!(
15470                menu.selected_item == 0,
15471                "expected PageUp to select the first item from the context menu"
15472            );
15473        } else {
15474            panic!("expected completion menu to stay open after PageUp");
15475        }
15476    });
15477}
15478
15479#[gpui::test]
15480async fn test_as_is_completions(cx: &mut TestAppContext) {
15481    init_test(cx, |_| {});
15482    let mut cx = EditorLspTestContext::new_rust(
15483        lsp::ServerCapabilities {
15484            completion_provider: Some(lsp::CompletionOptions {
15485                ..Default::default()
15486            }),
15487            ..Default::default()
15488        },
15489        cx,
15490    )
15491    .await;
15492    cx.lsp
15493        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15494            Ok(Some(lsp::CompletionResponse::Array(vec![
15495                lsp::CompletionItem {
15496                    label: "unsafe".into(),
15497                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15498                        range: lsp::Range {
15499                            start: lsp::Position {
15500                                line: 1,
15501                                character: 2,
15502                            },
15503                            end: lsp::Position {
15504                                line: 1,
15505                                character: 3,
15506                            },
15507                        },
15508                        new_text: "unsafe".to_string(),
15509                    })),
15510                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
15511                    ..Default::default()
15512                },
15513            ])))
15514        });
15515    cx.set_state("fn a() {}\n");
15516    cx.executor().run_until_parked();
15517    cx.update_editor(|editor, window, cx| {
15518        editor.trigger_completion_on_input("n", true, window, cx)
15519    });
15520    cx.executor().run_until_parked();
15521
15522    cx.update_editor(|editor, window, cx| {
15523        editor.confirm_completion(&Default::default(), window, cx)
15524    });
15525    cx.executor().run_until_parked();
15526    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
15527}
15528
15529#[gpui::test]
15530async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
15531    init_test(cx, |_| {});
15532    let language =
15533        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
15534    let mut cx = EditorLspTestContext::new(
15535        language,
15536        lsp::ServerCapabilities {
15537            completion_provider: Some(lsp::CompletionOptions {
15538                ..lsp::CompletionOptions::default()
15539            }),
15540            ..lsp::ServerCapabilities::default()
15541        },
15542        cx,
15543    )
15544    .await;
15545
15546    cx.set_state(
15547        "#ifndef BAR_H
15548#define BAR_H
15549
15550#include <stdbool.h>
15551
15552int fn_branch(bool do_branch1, bool do_branch2);
15553
15554#endif // BAR_H
15555ˇ",
15556    );
15557    cx.executor().run_until_parked();
15558    cx.update_editor(|editor, window, cx| {
15559        editor.handle_input("#", window, cx);
15560    });
15561    cx.executor().run_until_parked();
15562    cx.update_editor(|editor, window, cx| {
15563        editor.handle_input("i", window, cx);
15564    });
15565    cx.executor().run_until_parked();
15566    cx.update_editor(|editor, window, cx| {
15567        editor.handle_input("n", window, cx);
15568    });
15569    cx.executor().run_until_parked();
15570    cx.assert_editor_state(
15571        "#ifndef BAR_H
15572#define BAR_H
15573
15574#include <stdbool.h>
15575
15576int fn_branch(bool do_branch1, bool do_branch2);
15577
15578#endif // BAR_H
15579#inˇ",
15580    );
15581
15582    cx.lsp
15583        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15584            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15585                is_incomplete: false,
15586                item_defaults: None,
15587                items: vec![lsp::CompletionItem {
15588                    kind: Some(lsp::CompletionItemKind::SNIPPET),
15589                    label_details: Some(lsp::CompletionItemLabelDetails {
15590                        detail: Some("header".to_string()),
15591                        description: None,
15592                    }),
15593                    label: " include".to_string(),
15594                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15595                        range: lsp::Range {
15596                            start: lsp::Position {
15597                                line: 8,
15598                                character: 1,
15599                            },
15600                            end: lsp::Position {
15601                                line: 8,
15602                                character: 1,
15603                            },
15604                        },
15605                        new_text: "include \"$0\"".to_string(),
15606                    })),
15607                    sort_text: Some("40b67681include".to_string()),
15608                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15609                    filter_text: Some("include".to_string()),
15610                    insert_text: Some("include \"$0\"".to_string()),
15611                    ..lsp::CompletionItem::default()
15612                }],
15613            })))
15614        });
15615    cx.update_editor(|editor, window, cx| {
15616        editor.show_completions(&ShowCompletions, window, cx);
15617    });
15618    cx.executor().run_until_parked();
15619    cx.update_editor(|editor, window, cx| {
15620        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15621    });
15622    cx.executor().run_until_parked();
15623    cx.assert_editor_state(
15624        "#ifndef BAR_H
15625#define BAR_H
15626
15627#include <stdbool.h>
15628
15629int fn_branch(bool do_branch1, bool do_branch2);
15630
15631#endif // BAR_H
15632#include \"ˇ\"",
15633    );
15634
15635    cx.lsp
15636        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15637            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15638                is_incomplete: true,
15639                item_defaults: None,
15640                items: vec![lsp::CompletionItem {
15641                    kind: Some(lsp::CompletionItemKind::FILE),
15642                    label: "AGL/".to_string(),
15643                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15644                        range: lsp::Range {
15645                            start: lsp::Position {
15646                                line: 8,
15647                                character: 10,
15648                            },
15649                            end: lsp::Position {
15650                                line: 8,
15651                                character: 11,
15652                            },
15653                        },
15654                        new_text: "AGL/".to_string(),
15655                    })),
15656                    sort_text: Some("40b67681AGL/".to_string()),
15657                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15658                    filter_text: Some("AGL/".to_string()),
15659                    insert_text: Some("AGL/".to_string()),
15660                    ..lsp::CompletionItem::default()
15661                }],
15662            })))
15663        });
15664    cx.update_editor(|editor, window, cx| {
15665        editor.show_completions(&ShowCompletions, window, cx);
15666    });
15667    cx.executor().run_until_parked();
15668    cx.update_editor(|editor, window, cx| {
15669        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15670    });
15671    cx.executor().run_until_parked();
15672    cx.assert_editor_state(
15673        r##"#ifndef BAR_H
15674#define BAR_H
15675
15676#include <stdbool.h>
15677
15678int fn_branch(bool do_branch1, bool do_branch2);
15679
15680#endif // BAR_H
15681#include "AGL/ˇ"##,
15682    );
15683
15684    cx.update_editor(|editor, window, cx| {
15685        editor.handle_input("\"", window, cx);
15686    });
15687    cx.executor().run_until_parked();
15688    cx.assert_editor_state(
15689        r##"#ifndef BAR_H
15690#define BAR_H
15691
15692#include <stdbool.h>
15693
15694int fn_branch(bool do_branch1, bool do_branch2);
15695
15696#endif // BAR_H
15697#include "AGL/"ˇ"##,
15698    );
15699}
15700
15701#[gpui::test]
15702async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
15703    init_test(cx, |_| {});
15704
15705    let mut cx = EditorLspTestContext::new_rust(
15706        lsp::ServerCapabilities {
15707            completion_provider: Some(lsp::CompletionOptions {
15708                trigger_characters: Some(vec![".".to_string()]),
15709                resolve_provider: Some(true),
15710                ..Default::default()
15711            }),
15712            ..Default::default()
15713        },
15714        cx,
15715    )
15716    .await;
15717
15718    cx.set_state("fn main() { let a = 2ˇ; }");
15719    cx.simulate_keystroke(".");
15720    let completion_item = lsp::CompletionItem {
15721        label: "Some".into(),
15722        kind: Some(lsp::CompletionItemKind::SNIPPET),
15723        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15724        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15725            kind: lsp::MarkupKind::Markdown,
15726            value: "```rust\nSome(2)\n```".to_string(),
15727        })),
15728        deprecated: Some(false),
15729        sort_text: Some("Some".to_string()),
15730        filter_text: Some("Some".to_string()),
15731        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15732        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15733            range: lsp::Range {
15734                start: lsp::Position {
15735                    line: 0,
15736                    character: 22,
15737                },
15738                end: lsp::Position {
15739                    line: 0,
15740                    character: 22,
15741                },
15742            },
15743            new_text: "Some(2)".to_string(),
15744        })),
15745        additional_text_edits: Some(vec![lsp::TextEdit {
15746            range: lsp::Range {
15747                start: lsp::Position {
15748                    line: 0,
15749                    character: 20,
15750                },
15751                end: lsp::Position {
15752                    line: 0,
15753                    character: 22,
15754                },
15755            },
15756            new_text: "".to_string(),
15757        }]),
15758        ..Default::default()
15759    };
15760
15761    let closure_completion_item = completion_item.clone();
15762    let counter = Arc::new(AtomicUsize::new(0));
15763    let counter_clone = counter.clone();
15764    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15765        let task_completion_item = closure_completion_item.clone();
15766        counter_clone.fetch_add(1, atomic::Ordering::Release);
15767        async move {
15768            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15769                is_incomplete: true,
15770                item_defaults: None,
15771                items: vec![task_completion_item],
15772            })))
15773        }
15774    });
15775
15776    cx.condition(|editor, _| editor.context_menu_visible())
15777        .await;
15778    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
15779    assert!(request.next().await.is_some());
15780    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15781
15782    cx.simulate_keystrokes("S o m");
15783    cx.condition(|editor, _| editor.context_menu_visible())
15784        .await;
15785    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
15786    assert!(request.next().await.is_some());
15787    assert!(request.next().await.is_some());
15788    assert!(request.next().await.is_some());
15789    request.close();
15790    assert!(request.next().await.is_none());
15791    assert_eq!(
15792        counter.load(atomic::Ordering::Acquire),
15793        4,
15794        "With the completions menu open, only one LSP request should happen per input"
15795    );
15796}
15797
15798#[gpui::test]
15799async fn test_toggle_comment(cx: &mut TestAppContext) {
15800    init_test(cx, |_| {});
15801    let mut cx = EditorTestContext::new(cx).await;
15802    let language = Arc::new(Language::new(
15803        LanguageConfig {
15804            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15805            ..Default::default()
15806        },
15807        Some(tree_sitter_rust::LANGUAGE.into()),
15808    ));
15809    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15810
15811    // If multiple selections intersect a line, the line is only toggled once.
15812    cx.set_state(indoc! {"
15813        fn a() {
15814            «//b();
15815            ˇ»// «c();
15816            //ˇ»  d();
15817        }
15818    "});
15819
15820    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15821
15822    cx.assert_editor_state(indoc! {"
15823        fn a() {
15824            «b();
15825            c();
15826            ˇ» d();
15827        }
15828    "});
15829
15830    // The comment prefix is inserted at the same column for every line in a
15831    // selection.
15832    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15833
15834    cx.assert_editor_state(indoc! {"
15835        fn a() {
15836            // «b();
15837            // c();
15838            ˇ»//  d();
15839        }
15840    "});
15841
15842    // If a selection ends at the beginning of a line, that line is not toggled.
15843    cx.set_selections_state(indoc! {"
15844        fn a() {
15845            // b();
15846            «// c();
15847        ˇ»    //  d();
15848        }
15849    "});
15850
15851    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15852
15853    cx.assert_editor_state(indoc! {"
15854        fn a() {
15855            // b();
15856            «c();
15857        ˇ»    //  d();
15858        }
15859    "});
15860
15861    // If a selection span a single line and is empty, the line is toggled.
15862    cx.set_state(indoc! {"
15863        fn a() {
15864            a();
15865            b();
15866        ˇ
15867        }
15868    "});
15869
15870    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15871
15872    cx.assert_editor_state(indoc! {"
15873        fn a() {
15874            a();
15875            b();
15876        //•ˇ
15877        }
15878    "});
15879
15880    // If a selection span multiple lines, empty lines are not toggled.
15881    cx.set_state(indoc! {"
15882        fn a() {
15883            «a();
15884
15885            c();ˇ»
15886        }
15887    "});
15888
15889    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15890
15891    cx.assert_editor_state(indoc! {"
15892        fn a() {
15893            // «a();
15894
15895            // c();ˇ»
15896        }
15897    "});
15898
15899    // If a selection includes multiple comment prefixes, all lines are uncommented.
15900    cx.set_state(indoc! {"
15901        fn a() {
15902            «// a();
15903            /// b();
15904            //! c();ˇ»
15905        }
15906    "});
15907
15908    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15909
15910    cx.assert_editor_state(indoc! {"
15911        fn a() {
15912            «a();
15913            b();
15914            c();ˇ»
15915        }
15916    "});
15917}
15918
15919#[gpui::test]
15920async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
15921    init_test(cx, |_| {});
15922    let mut cx = EditorTestContext::new(cx).await;
15923    let language = Arc::new(Language::new(
15924        LanguageConfig {
15925            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15926            ..Default::default()
15927        },
15928        Some(tree_sitter_rust::LANGUAGE.into()),
15929    ));
15930    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15931
15932    let toggle_comments = &ToggleComments {
15933        advance_downwards: false,
15934        ignore_indent: true,
15935    };
15936
15937    // If multiple selections intersect a line, the line is only toggled once.
15938    cx.set_state(indoc! {"
15939        fn a() {
15940        //    «b();
15941        //    c();
15942        //    ˇ» d();
15943        }
15944    "});
15945
15946    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15947
15948    cx.assert_editor_state(indoc! {"
15949        fn a() {
15950            «b();
15951            c();
15952            ˇ» d();
15953        }
15954    "});
15955
15956    // The comment prefix is inserted at the beginning of each line
15957    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15958
15959    cx.assert_editor_state(indoc! {"
15960        fn a() {
15961        //    «b();
15962        //    c();
15963        //    ˇ» d();
15964        }
15965    "});
15966
15967    // If a selection ends at the beginning of a line, that line is not toggled.
15968    cx.set_selections_state(indoc! {"
15969        fn a() {
15970        //    b();
15971        //    «c();
15972        ˇ»//     d();
15973        }
15974    "});
15975
15976    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15977
15978    cx.assert_editor_state(indoc! {"
15979        fn a() {
15980        //    b();
15981            «c();
15982        ˇ»//     d();
15983        }
15984    "});
15985
15986    // If a selection span a single line and is empty, the line is toggled.
15987    cx.set_state(indoc! {"
15988        fn a() {
15989            a();
15990            b();
15991        ˇ
15992        }
15993    "});
15994
15995    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15996
15997    cx.assert_editor_state(indoc! {"
15998        fn a() {
15999            a();
16000            b();
16001        //ˇ
16002        }
16003    "});
16004
16005    // If a selection span multiple lines, empty lines are not toggled.
16006    cx.set_state(indoc! {"
16007        fn a() {
16008            «a();
16009
16010            c();ˇ»
16011        }
16012    "});
16013
16014    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16015
16016    cx.assert_editor_state(indoc! {"
16017        fn a() {
16018        //    «a();
16019
16020        //    c();ˇ»
16021        }
16022    "});
16023
16024    // If a selection includes multiple comment prefixes, all lines are uncommented.
16025    cx.set_state(indoc! {"
16026        fn a() {
16027        //    «a();
16028        ///    b();
16029        //!    c();ˇ»
16030        }
16031    "});
16032
16033    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16034
16035    cx.assert_editor_state(indoc! {"
16036        fn a() {
16037            «a();
16038            b();
16039            c();ˇ»
16040        }
16041    "});
16042}
16043
16044#[gpui::test]
16045async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
16046    init_test(cx, |_| {});
16047
16048    let language = Arc::new(Language::new(
16049        LanguageConfig {
16050            line_comments: vec!["// ".into()],
16051            ..Default::default()
16052        },
16053        Some(tree_sitter_rust::LANGUAGE.into()),
16054    ));
16055
16056    let mut cx = EditorTestContext::new(cx).await;
16057
16058    cx.language_registry().add(language.clone());
16059    cx.update_buffer(|buffer, cx| {
16060        buffer.set_language(Some(language), cx);
16061    });
16062
16063    let toggle_comments = &ToggleComments {
16064        advance_downwards: true,
16065        ignore_indent: false,
16066    };
16067
16068    // Single cursor on one line -> advance
16069    // Cursor moves horizontally 3 characters as well on non-blank line
16070    cx.set_state(indoc!(
16071        "fn a() {
16072             ˇdog();
16073             cat();
16074        }"
16075    ));
16076    cx.update_editor(|editor, window, cx| {
16077        editor.toggle_comments(toggle_comments, window, cx);
16078    });
16079    cx.assert_editor_state(indoc!(
16080        "fn a() {
16081             // dog();
16082             catˇ();
16083        }"
16084    ));
16085
16086    // Single selection on one line -> don't advance
16087    cx.set_state(indoc!(
16088        "fn a() {
16089             «dog()ˇ»;
16090             cat();
16091        }"
16092    ));
16093    cx.update_editor(|editor, window, cx| {
16094        editor.toggle_comments(toggle_comments, window, cx);
16095    });
16096    cx.assert_editor_state(indoc!(
16097        "fn a() {
16098             // «dog()ˇ»;
16099             cat();
16100        }"
16101    ));
16102
16103    // Multiple cursors on one line -> advance
16104    cx.set_state(indoc!(
16105        "fn a() {
16106             ˇdˇog();
16107             cat();
16108        }"
16109    ));
16110    cx.update_editor(|editor, window, cx| {
16111        editor.toggle_comments(toggle_comments, window, cx);
16112    });
16113    cx.assert_editor_state(indoc!(
16114        "fn a() {
16115             // dog();
16116             catˇ(ˇ);
16117        }"
16118    ));
16119
16120    // Multiple cursors on one line, with selection -> don't advance
16121    cx.set_state(indoc!(
16122        "fn a() {
16123             ˇdˇog«()ˇ»;
16124             cat();
16125        }"
16126    ));
16127    cx.update_editor(|editor, window, cx| {
16128        editor.toggle_comments(toggle_comments, window, cx);
16129    });
16130    cx.assert_editor_state(indoc!(
16131        "fn a() {
16132             // ˇdˇog«()ˇ»;
16133             cat();
16134        }"
16135    ));
16136
16137    // Single cursor on one line -> advance
16138    // Cursor moves to column 0 on blank line
16139    cx.set_state(indoc!(
16140        "fn a() {
16141             ˇdog();
16142
16143             cat();
16144        }"
16145    ));
16146    cx.update_editor(|editor, window, cx| {
16147        editor.toggle_comments(toggle_comments, window, cx);
16148    });
16149    cx.assert_editor_state(indoc!(
16150        "fn a() {
16151             // dog();
16152        ˇ
16153             cat();
16154        }"
16155    ));
16156
16157    // Single cursor on one line -> advance
16158    // Cursor starts and ends at column 0
16159    cx.set_state(indoc!(
16160        "fn a() {
16161         ˇ    dog();
16162             cat();
16163        }"
16164    ));
16165    cx.update_editor(|editor, window, cx| {
16166        editor.toggle_comments(toggle_comments, window, cx);
16167    });
16168    cx.assert_editor_state(indoc!(
16169        "fn a() {
16170             // dog();
16171         ˇ    cat();
16172        }"
16173    ));
16174}
16175
16176#[gpui::test]
16177async fn test_toggle_block_comment(cx: &mut TestAppContext) {
16178    init_test(cx, |_| {});
16179
16180    let mut cx = EditorTestContext::new(cx).await;
16181
16182    let html_language = Arc::new(
16183        Language::new(
16184            LanguageConfig {
16185                name: "HTML".into(),
16186                block_comment: Some(BlockCommentConfig {
16187                    start: "<!-- ".into(),
16188                    prefix: "".into(),
16189                    end: " -->".into(),
16190                    tab_size: 0,
16191                }),
16192                ..Default::default()
16193            },
16194            Some(tree_sitter_html::LANGUAGE.into()),
16195        )
16196        .with_injection_query(
16197            r#"
16198            (script_element
16199                (raw_text) @injection.content
16200                (#set! injection.language "javascript"))
16201            "#,
16202        )
16203        .unwrap(),
16204    );
16205
16206    let javascript_language = Arc::new(Language::new(
16207        LanguageConfig {
16208            name: "JavaScript".into(),
16209            line_comments: vec!["// ".into()],
16210            ..Default::default()
16211        },
16212        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
16213    ));
16214
16215    cx.language_registry().add(html_language.clone());
16216    cx.language_registry().add(javascript_language);
16217    cx.update_buffer(|buffer, cx| {
16218        buffer.set_language(Some(html_language), cx);
16219    });
16220
16221    // Toggle comments for empty selections
16222    cx.set_state(
16223        &r#"
16224            <p>A</p>ˇ
16225            <p>B</p>ˇ
16226            <p>C</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        "#
16239        .unindent(),
16240    );
16241    cx.update_editor(|editor, window, cx| {
16242        editor.toggle_comments(&ToggleComments::default(), window, cx)
16243    });
16244    cx.assert_editor_state(
16245        &r#"
16246            <p>A</p>ˇ
16247            <p>B</p>ˇ
16248            <p>C</p>ˇ
16249        "#
16250        .unindent(),
16251    );
16252
16253    // Toggle comments for mixture of empty and non-empty selections, where
16254    // multiple selections occupy a given line.
16255    cx.set_state(
16256        &r#"
16257            <p>A«</p>
16258            <p>ˇ»B</p>ˇ
16259            <p>C«</p>
16260            <p>ˇ»D</p>ˇ
16261        "#
16262        .unindent(),
16263    );
16264
16265    cx.update_editor(|editor, window, cx| {
16266        editor.toggle_comments(&ToggleComments::default(), window, cx)
16267    });
16268    cx.assert_editor_state(
16269        &r#"
16270            <!-- <p>A«</p>
16271            <p>ˇ»B</p>ˇ -->
16272            <!-- <p>C«</p>
16273            <p>ˇ»D</p>ˇ -->
16274        "#
16275        .unindent(),
16276    );
16277    cx.update_editor(|editor, window, cx| {
16278        editor.toggle_comments(&ToggleComments::default(), window, cx)
16279    });
16280    cx.assert_editor_state(
16281        &r#"
16282            <p>A«</p>
16283            <p>ˇ»B</p>ˇ
16284            <p>C«</p>
16285            <p>ˇ»D</p>ˇ
16286        "#
16287        .unindent(),
16288    );
16289
16290    // Toggle comments when different languages are active for different
16291    // selections.
16292    cx.set_state(
16293        &r#"
16294            ˇ<script>
16295                ˇvar x = new Y();
16296            ˇ</script>
16297        "#
16298        .unindent(),
16299    );
16300    cx.executor().run_until_parked();
16301    cx.update_editor(|editor, window, cx| {
16302        editor.toggle_comments(&ToggleComments::default(), window, cx)
16303    });
16304    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
16305    // Uncommenting and commenting from this position brings in even more wrong artifacts.
16306    cx.assert_editor_state(
16307        &r#"
16308            <!-- ˇ<script> -->
16309                // ˇvar x = new Y();
16310            <!-- ˇ</script> -->
16311        "#
16312        .unindent(),
16313    );
16314}
16315
16316#[gpui::test]
16317fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
16318    init_test(cx, |_| {});
16319
16320    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16321    let multibuffer = cx.new(|cx| {
16322        let mut multibuffer = MultiBuffer::new(ReadWrite);
16323        multibuffer.push_excerpts(
16324            buffer.clone(),
16325            [
16326                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
16327                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
16328            ],
16329            cx,
16330        );
16331        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
16332        multibuffer
16333    });
16334
16335    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16336    editor.update_in(cx, |editor, window, cx| {
16337        assert_eq!(editor.text(cx), "aaaa\nbbbb");
16338        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16339            s.select_ranges([
16340                Point::new(0, 0)..Point::new(0, 0),
16341                Point::new(1, 0)..Point::new(1, 0),
16342            ])
16343        });
16344
16345        editor.handle_input("X", window, cx);
16346        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
16347        assert_eq!(
16348            editor.selections.ranges(&editor.display_snapshot(cx)),
16349            [
16350                Point::new(0, 1)..Point::new(0, 1),
16351                Point::new(1, 1)..Point::new(1, 1),
16352            ]
16353        );
16354
16355        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
16356        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16357            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
16358        });
16359        editor.backspace(&Default::default(), window, cx);
16360        assert_eq!(editor.text(cx), "Xa\nbbb");
16361        assert_eq!(
16362            editor.selections.ranges(&editor.display_snapshot(cx)),
16363            [Point::new(1, 0)..Point::new(1, 0)]
16364        );
16365
16366        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16367            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
16368        });
16369        editor.backspace(&Default::default(), window, cx);
16370        assert_eq!(editor.text(cx), "X\nbb");
16371        assert_eq!(
16372            editor.selections.ranges(&editor.display_snapshot(cx)),
16373            [Point::new(0, 1)..Point::new(0, 1)]
16374        );
16375    });
16376}
16377
16378#[gpui::test]
16379fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
16380    init_test(cx, |_| {});
16381
16382    let markers = vec![('[', ']').into(), ('(', ')').into()];
16383    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
16384        indoc! {"
16385            [aaaa
16386            (bbbb]
16387            cccc)",
16388        },
16389        markers.clone(),
16390    );
16391    let excerpt_ranges = markers.into_iter().map(|marker| {
16392        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
16393        ExcerptRange::new(context)
16394    });
16395    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
16396    let multibuffer = cx.new(|cx| {
16397        let mut multibuffer = MultiBuffer::new(ReadWrite);
16398        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
16399        multibuffer
16400    });
16401
16402    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16403    editor.update_in(cx, |editor, window, cx| {
16404        let (expected_text, selection_ranges) = marked_text_ranges(
16405            indoc! {"
16406                aaaa
16407                bˇbbb
16408                bˇbbˇb
16409                cccc"
16410            },
16411            true,
16412        );
16413        assert_eq!(editor.text(cx), expected_text);
16414        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16415            s.select_ranges(selection_ranges)
16416        });
16417
16418        editor.handle_input("X", window, cx);
16419
16420        let (expected_text, expected_selections) = marked_text_ranges(
16421            indoc! {"
16422                aaaa
16423                bXˇbbXb
16424                bXˇbbXˇb
16425                cccc"
16426            },
16427            false,
16428        );
16429        assert_eq!(editor.text(cx), expected_text);
16430        assert_eq!(
16431            editor.selections.ranges(&editor.display_snapshot(cx)),
16432            expected_selections
16433        );
16434
16435        editor.newline(&Newline, window, cx);
16436        let (expected_text, expected_selections) = marked_text_ranges(
16437            indoc! {"
16438                aaaa
16439                bX
16440                ˇbbX
16441                b
16442                bX
16443                ˇbbX
16444                ˇb
16445                cccc"
16446            },
16447            false,
16448        );
16449        assert_eq!(editor.text(cx), expected_text);
16450        assert_eq!(
16451            editor.selections.ranges(&editor.display_snapshot(cx)),
16452            expected_selections
16453        );
16454    });
16455}
16456
16457#[gpui::test]
16458fn test_refresh_selections(cx: &mut TestAppContext) {
16459    init_test(cx, |_| {});
16460
16461    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16462    let mut excerpt1_id = None;
16463    let multibuffer = cx.new(|cx| {
16464        let mut multibuffer = MultiBuffer::new(ReadWrite);
16465        excerpt1_id = multibuffer
16466            .push_excerpts(
16467                buffer.clone(),
16468                [
16469                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16470                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16471                ],
16472                cx,
16473            )
16474            .into_iter()
16475            .next();
16476        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16477        multibuffer
16478    });
16479
16480    let editor = cx.add_window(|window, cx| {
16481        let mut editor = build_editor(multibuffer.clone(), window, cx);
16482        let snapshot = editor.snapshot(window, cx);
16483        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16484            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
16485        });
16486        editor.begin_selection(
16487            Point::new(2, 1).to_display_point(&snapshot),
16488            true,
16489            1,
16490            window,
16491            cx,
16492        );
16493        assert_eq!(
16494            editor.selections.ranges(&editor.display_snapshot(cx)),
16495            [
16496                Point::new(1, 3)..Point::new(1, 3),
16497                Point::new(2, 1)..Point::new(2, 1),
16498            ]
16499        );
16500        editor
16501    });
16502
16503    // Refreshing selections is a no-op when excerpts haven't changed.
16504    _ = editor.update(cx, |editor, window, cx| {
16505        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16506        assert_eq!(
16507            editor.selections.ranges(&editor.display_snapshot(cx)),
16508            [
16509                Point::new(1, 3)..Point::new(1, 3),
16510                Point::new(2, 1)..Point::new(2, 1),
16511            ]
16512        );
16513    });
16514
16515    multibuffer.update(cx, |multibuffer, cx| {
16516        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16517    });
16518    _ = editor.update(cx, |editor, window, cx| {
16519        // Removing an excerpt causes the first selection to become degenerate.
16520        assert_eq!(
16521            editor.selections.ranges(&editor.display_snapshot(cx)),
16522            [
16523                Point::new(0, 0)..Point::new(0, 0),
16524                Point::new(0, 1)..Point::new(0, 1)
16525            ]
16526        );
16527
16528        // Refreshing selections will relocate the first selection to the original buffer
16529        // location.
16530        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16531        assert_eq!(
16532            editor.selections.ranges(&editor.display_snapshot(cx)),
16533            [
16534                Point::new(0, 1)..Point::new(0, 1),
16535                Point::new(0, 3)..Point::new(0, 3)
16536            ]
16537        );
16538        assert!(editor.selections.pending_anchor().is_some());
16539    });
16540}
16541
16542#[gpui::test]
16543fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
16544    init_test(cx, |_| {});
16545
16546    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16547    let mut excerpt1_id = None;
16548    let multibuffer = cx.new(|cx| {
16549        let mut multibuffer = MultiBuffer::new(ReadWrite);
16550        excerpt1_id = multibuffer
16551            .push_excerpts(
16552                buffer.clone(),
16553                [
16554                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16555                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16556                ],
16557                cx,
16558            )
16559            .into_iter()
16560            .next();
16561        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16562        multibuffer
16563    });
16564
16565    let editor = cx.add_window(|window, cx| {
16566        let mut editor = build_editor(multibuffer.clone(), window, cx);
16567        let snapshot = editor.snapshot(window, cx);
16568        editor.begin_selection(
16569            Point::new(1, 3).to_display_point(&snapshot),
16570            false,
16571            1,
16572            window,
16573            cx,
16574        );
16575        assert_eq!(
16576            editor.selections.ranges(&editor.display_snapshot(cx)),
16577            [Point::new(1, 3)..Point::new(1, 3)]
16578        );
16579        editor
16580    });
16581
16582    multibuffer.update(cx, |multibuffer, cx| {
16583        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16584    });
16585    _ = editor.update(cx, |editor, window, cx| {
16586        assert_eq!(
16587            editor.selections.ranges(&editor.display_snapshot(cx)),
16588            [Point::new(0, 0)..Point::new(0, 0)]
16589        );
16590
16591        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
16592        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16593        assert_eq!(
16594            editor.selections.ranges(&editor.display_snapshot(cx)),
16595            [Point::new(0, 3)..Point::new(0, 3)]
16596        );
16597        assert!(editor.selections.pending_anchor().is_some());
16598    });
16599}
16600
16601#[gpui::test]
16602async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
16603    init_test(cx, |_| {});
16604
16605    let language = Arc::new(
16606        Language::new(
16607            LanguageConfig {
16608                brackets: BracketPairConfig {
16609                    pairs: vec![
16610                        BracketPair {
16611                            start: "{".to_string(),
16612                            end: "}".to_string(),
16613                            close: true,
16614                            surround: true,
16615                            newline: true,
16616                        },
16617                        BracketPair {
16618                            start: "/* ".to_string(),
16619                            end: " */".to_string(),
16620                            close: true,
16621                            surround: true,
16622                            newline: true,
16623                        },
16624                    ],
16625                    ..Default::default()
16626                },
16627                ..Default::default()
16628            },
16629            Some(tree_sitter_rust::LANGUAGE.into()),
16630        )
16631        .with_indents_query("")
16632        .unwrap(),
16633    );
16634
16635    let text = concat!(
16636        "{   }\n",     //
16637        "  x\n",       //
16638        "  /*   */\n", //
16639        "x\n",         //
16640        "{{} }\n",     //
16641    );
16642
16643    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
16644    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16645    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16646    editor
16647        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
16648        .await;
16649
16650    editor.update_in(cx, |editor, window, cx| {
16651        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16652            s.select_display_ranges([
16653                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
16654                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
16655                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
16656            ])
16657        });
16658        editor.newline(&Newline, window, cx);
16659
16660        assert_eq!(
16661            editor.buffer().read(cx).read(cx).text(),
16662            concat!(
16663                "{ \n",    // Suppress rustfmt
16664                "\n",      //
16665                "}\n",     //
16666                "  x\n",   //
16667                "  /* \n", //
16668                "  \n",    //
16669                "  */\n",  //
16670                "x\n",     //
16671                "{{} \n",  //
16672                "}\n",     //
16673            )
16674        );
16675    });
16676}
16677
16678#[gpui::test]
16679fn test_highlighted_ranges(cx: &mut TestAppContext) {
16680    init_test(cx, |_| {});
16681
16682    let editor = cx.add_window(|window, cx| {
16683        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
16684        build_editor(buffer, window, cx)
16685    });
16686
16687    _ = editor.update(cx, |editor, window, cx| {
16688        struct Type1;
16689        struct Type2;
16690
16691        let buffer = editor.buffer.read(cx).snapshot(cx);
16692
16693        let anchor_range =
16694            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
16695
16696        editor.highlight_background::<Type1>(
16697            &[
16698                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
16699                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
16700                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
16701                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
16702            ],
16703            |_| Hsla::red(),
16704            cx,
16705        );
16706        editor.highlight_background::<Type2>(
16707            &[
16708                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
16709                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
16710                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
16711                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
16712            ],
16713            |_| Hsla::green(),
16714            cx,
16715        );
16716
16717        let snapshot = editor.snapshot(window, cx);
16718        let highlighted_ranges = editor.sorted_background_highlights_in_range(
16719            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
16720            &snapshot,
16721            cx.theme(),
16722        );
16723        assert_eq!(
16724            highlighted_ranges,
16725            &[
16726                (
16727                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
16728                    Hsla::green(),
16729                ),
16730                (
16731                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
16732                    Hsla::red(),
16733                ),
16734                (
16735                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
16736                    Hsla::green(),
16737                ),
16738                (
16739                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16740                    Hsla::red(),
16741                ),
16742            ]
16743        );
16744        assert_eq!(
16745            editor.sorted_background_highlights_in_range(
16746                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
16747                &snapshot,
16748                cx.theme(),
16749            ),
16750            &[(
16751                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16752                Hsla::red(),
16753            )]
16754        );
16755    });
16756}
16757
16758#[gpui::test]
16759async fn test_following(cx: &mut TestAppContext) {
16760    init_test(cx, |_| {});
16761
16762    let fs = FakeFs::new(cx.executor());
16763    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16764
16765    let buffer = project.update(cx, |project, cx| {
16766        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
16767        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
16768    });
16769    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
16770    let follower = cx.update(|cx| {
16771        cx.open_window(
16772            WindowOptions {
16773                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
16774                    gpui::Point::new(px(0.), px(0.)),
16775                    gpui::Point::new(px(10.), px(80.)),
16776                ))),
16777                ..Default::default()
16778            },
16779            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
16780        )
16781        .unwrap()
16782    });
16783
16784    let is_still_following = Rc::new(RefCell::new(true));
16785    let follower_edit_event_count = Rc::new(RefCell::new(0));
16786    let pending_update = Rc::new(RefCell::new(None));
16787    let leader_entity = leader.root(cx).unwrap();
16788    let follower_entity = follower.root(cx).unwrap();
16789    _ = follower.update(cx, {
16790        let update = pending_update.clone();
16791        let is_still_following = is_still_following.clone();
16792        let follower_edit_event_count = follower_edit_event_count.clone();
16793        |_, window, cx| {
16794            cx.subscribe_in(
16795                &leader_entity,
16796                window,
16797                move |_, leader, event, window, cx| {
16798                    leader.read(cx).add_event_to_update_proto(
16799                        event,
16800                        &mut update.borrow_mut(),
16801                        window,
16802                        cx,
16803                    );
16804                },
16805            )
16806            .detach();
16807
16808            cx.subscribe_in(
16809                &follower_entity,
16810                window,
16811                move |_, _, event: &EditorEvent, _window, _cx| {
16812                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
16813                        *is_still_following.borrow_mut() = false;
16814                    }
16815
16816                    if let EditorEvent::BufferEdited = event {
16817                        *follower_edit_event_count.borrow_mut() += 1;
16818                    }
16819                },
16820            )
16821            .detach();
16822        }
16823    });
16824
16825    // Update the selections only
16826    _ = leader.update(cx, |leader, window, cx| {
16827        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16828            s.select_ranges([1..1])
16829        });
16830    });
16831    follower
16832        .update(cx, |follower, window, cx| {
16833            follower.apply_update_proto(
16834                &project,
16835                pending_update.borrow_mut().take().unwrap(),
16836                window,
16837                cx,
16838            )
16839        })
16840        .unwrap()
16841        .await
16842        .unwrap();
16843    _ = follower.update(cx, |follower, _, cx| {
16844        assert_eq!(
16845            follower.selections.ranges(&follower.display_snapshot(cx)),
16846            vec![1..1]
16847        );
16848    });
16849    assert!(*is_still_following.borrow());
16850    assert_eq!(*follower_edit_event_count.borrow(), 0);
16851
16852    // Update the scroll position only
16853    _ = leader.update(cx, |leader, window, cx| {
16854        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16855    });
16856    follower
16857        .update(cx, |follower, window, cx| {
16858            follower.apply_update_proto(
16859                &project,
16860                pending_update.borrow_mut().take().unwrap(),
16861                window,
16862                cx,
16863            )
16864        })
16865        .unwrap()
16866        .await
16867        .unwrap();
16868    assert_eq!(
16869        follower
16870            .update(cx, |follower, _, cx| follower.scroll_position(cx))
16871            .unwrap(),
16872        gpui::Point::new(1.5, 3.5)
16873    );
16874    assert!(*is_still_following.borrow());
16875    assert_eq!(*follower_edit_event_count.borrow(), 0);
16876
16877    // Update the selections and scroll position. The follower's scroll position is updated
16878    // via autoscroll, not via the leader's exact scroll position.
16879    _ = leader.update(cx, |leader, window, cx| {
16880        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16881            s.select_ranges([0..0])
16882        });
16883        leader.request_autoscroll(Autoscroll::newest(), cx);
16884        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16885    });
16886    follower
16887        .update(cx, |follower, window, cx| {
16888            follower.apply_update_proto(
16889                &project,
16890                pending_update.borrow_mut().take().unwrap(),
16891                window,
16892                cx,
16893            )
16894        })
16895        .unwrap()
16896        .await
16897        .unwrap();
16898    _ = follower.update(cx, |follower, _, cx| {
16899        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
16900        assert_eq!(
16901            follower.selections.ranges(&follower.display_snapshot(cx)),
16902            vec![0..0]
16903        );
16904    });
16905    assert!(*is_still_following.borrow());
16906
16907    // Creating a pending selection that precedes another selection
16908    _ = leader.update(cx, |leader, window, cx| {
16909        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16910            s.select_ranges([1..1])
16911        });
16912        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
16913    });
16914    follower
16915        .update(cx, |follower, window, cx| {
16916            follower.apply_update_proto(
16917                &project,
16918                pending_update.borrow_mut().take().unwrap(),
16919                window,
16920                cx,
16921            )
16922        })
16923        .unwrap()
16924        .await
16925        .unwrap();
16926    _ = follower.update(cx, |follower, _, cx| {
16927        assert_eq!(
16928            follower.selections.ranges(&follower.display_snapshot(cx)),
16929            vec![0..0, 1..1]
16930        );
16931    });
16932    assert!(*is_still_following.borrow());
16933
16934    // Extend the pending selection so that it surrounds another selection
16935    _ = leader.update(cx, |leader, window, cx| {
16936        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
16937    });
16938    follower
16939        .update(cx, |follower, window, cx| {
16940            follower.apply_update_proto(
16941                &project,
16942                pending_update.borrow_mut().take().unwrap(),
16943                window,
16944                cx,
16945            )
16946        })
16947        .unwrap()
16948        .await
16949        .unwrap();
16950    _ = follower.update(cx, |follower, _, cx| {
16951        assert_eq!(
16952            follower.selections.ranges(&follower.display_snapshot(cx)),
16953            vec![0..2]
16954        );
16955    });
16956
16957    // Scrolling locally breaks the follow
16958    _ = follower.update(cx, |follower, window, cx| {
16959        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
16960        follower.set_scroll_anchor(
16961            ScrollAnchor {
16962                anchor: top_anchor,
16963                offset: gpui::Point::new(0.0, 0.5),
16964            },
16965            window,
16966            cx,
16967        );
16968    });
16969    assert!(!(*is_still_following.borrow()));
16970}
16971
16972#[gpui::test]
16973async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
16974    init_test(cx, |_| {});
16975
16976    let fs = FakeFs::new(cx.executor());
16977    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16978    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16979    let pane = workspace
16980        .update(cx, |workspace, _, _| workspace.active_pane().clone())
16981        .unwrap();
16982
16983    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16984
16985    let leader = pane.update_in(cx, |_, window, cx| {
16986        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
16987        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
16988    });
16989
16990    // Start following the editor when it has no excerpts.
16991    let mut state_message =
16992        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16993    let workspace_entity = workspace.root(cx).unwrap();
16994    let follower_1 = cx
16995        .update_window(*workspace.deref(), |_, window, cx| {
16996            Editor::from_state_proto(
16997                workspace_entity,
16998                ViewId {
16999                    creator: CollaboratorId::PeerId(PeerId::default()),
17000                    id: 0,
17001                },
17002                &mut state_message,
17003                window,
17004                cx,
17005            )
17006        })
17007        .unwrap()
17008        .unwrap()
17009        .await
17010        .unwrap();
17011
17012    let update_message = Rc::new(RefCell::new(None));
17013    follower_1.update_in(cx, {
17014        let update = update_message.clone();
17015        |_, window, cx| {
17016            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
17017                leader.read(cx).add_event_to_update_proto(
17018                    event,
17019                    &mut update.borrow_mut(),
17020                    window,
17021                    cx,
17022                );
17023            })
17024            .detach();
17025        }
17026    });
17027
17028    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
17029        (
17030            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
17031            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
17032        )
17033    });
17034
17035    // Insert some excerpts.
17036    leader.update(cx, |leader, cx| {
17037        leader.buffer.update(cx, |multibuffer, cx| {
17038            multibuffer.set_excerpts_for_path(
17039                PathKey::with_sort_prefix(1, rel_path("b.txt").into_arc()),
17040                buffer_1.clone(),
17041                vec![
17042                    Point::row_range(0..3),
17043                    Point::row_range(1..6),
17044                    Point::row_range(12..15),
17045                ],
17046                0,
17047                cx,
17048            );
17049            multibuffer.set_excerpts_for_path(
17050                PathKey::with_sort_prefix(1, rel_path("a.txt").into_arc()),
17051                buffer_2.clone(),
17052                vec![Point::row_range(0..6), Point::row_range(8..12)],
17053                0,
17054                cx,
17055            );
17056        });
17057    });
17058
17059    // Apply the update of adding the excerpts.
17060    follower_1
17061        .update_in(cx, |follower, window, cx| {
17062            follower.apply_update_proto(
17063                &project,
17064                update_message.borrow().clone().unwrap(),
17065                window,
17066                cx,
17067            )
17068        })
17069        .await
17070        .unwrap();
17071    assert_eq!(
17072        follower_1.update(cx, |editor, cx| editor.text(cx)),
17073        leader.update(cx, |editor, cx| editor.text(cx))
17074    );
17075    update_message.borrow_mut().take();
17076
17077    // Start following separately after it already has excerpts.
17078    let mut state_message =
17079        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
17080    let workspace_entity = workspace.root(cx).unwrap();
17081    let follower_2 = cx
17082        .update_window(*workspace.deref(), |_, window, cx| {
17083            Editor::from_state_proto(
17084                workspace_entity,
17085                ViewId {
17086                    creator: CollaboratorId::PeerId(PeerId::default()),
17087                    id: 0,
17088                },
17089                &mut state_message,
17090                window,
17091                cx,
17092            )
17093        })
17094        .unwrap()
17095        .unwrap()
17096        .await
17097        .unwrap();
17098    assert_eq!(
17099        follower_2.update(cx, |editor, cx| editor.text(cx)),
17100        leader.update(cx, |editor, cx| editor.text(cx))
17101    );
17102
17103    // Remove some excerpts.
17104    leader.update(cx, |leader, cx| {
17105        leader.buffer.update(cx, |multibuffer, cx| {
17106            let excerpt_ids = multibuffer.excerpt_ids();
17107            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
17108            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
17109        });
17110    });
17111
17112    // Apply the update of removing the excerpts.
17113    follower_1
17114        .update_in(cx, |follower, window, cx| {
17115            follower.apply_update_proto(
17116                &project,
17117                update_message.borrow().clone().unwrap(),
17118                window,
17119                cx,
17120            )
17121        })
17122        .await
17123        .unwrap();
17124    follower_2
17125        .update_in(cx, |follower, window, cx| {
17126            follower.apply_update_proto(
17127                &project,
17128                update_message.borrow().clone().unwrap(),
17129                window,
17130                cx,
17131            )
17132        })
17133        .await
17134        .unwrap();
17135    update_message.borrow_mut().take();
17136    assert_eq!(
17137        follower_1.update(cx, |editor, cx| editor.text(cx)),
17138        leader.update(cx, |editor, cx| editor.text(cx))
17139    );
17140}
17141
17142#[gpui::test]
17143async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17144    init_test(cx, |_| {});
17145
17146    let mut cx = EditorTestContext::new(cx).await;
17147    let lsp_store =
17148        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
17149
17150    cx.set_state(indoc! {"
17151        ˇfn func(abc def: i32) -> u32 {
17152        }
17153    "});
17154
17155    cx.update(|_, cx| {
17156        lsp_store.update(cx, |lsp_store, cx| {
17157            lsp_store
17158                .update_diagnostics(
17159                    LanguageServerId(0),
17160                    lsp::PublishDiagnosticsParams {
17161                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
17162                        version: None,
17163                        diagnostics: vec![
17164                            lsp::Diagnostic {
17165                                range: lsp::Range::new(
17166                                    lsp::Position::new(0, 11),
17167                                    lsp::Position::new(0, 12),
17168                                ),
17169                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17170                                ..Default::default()
17171                            },
17172                            lsp::Diagnostic {
17173                                range: lsp::Range::new(
17174                                    lsp::Position::new(0, 12),
17175                                    lsp::Position::new(0, 15),
17176                                ),
17177                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17178                                ..Default::default()
17179                            },
17180                            lsp::Diagnostic {
17181                                range: lsp::Range::new(
17182                                    lsp::Position::new(0, 25),
17183                                    lsp::Position::new(0, 28),
17184                                ),
17185                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17186                                ..Default::default()
17187                            },
17188                        ],
17189                    },
17190                    None,
17191                    DiagnosticSourceKind::Pushed,
17192                    &[],
17193                    cx,
17194                )
17195                .unwrap()
17196        });
17197    });
17198
17199    executor.run_until_parked();
17200
17201    cx.update_editor(|editor, window, cx| {
17202        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17203    });
17204
17205    cx.assert_editor_state(indoc! {"
17206        fn func(abc def: i32) -> ˇu32 {
17207        }
17208    "});
17209
17210    cx.update_editor(|editor, window, cx| {
17211        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17212    });
17213
17214    cx.assert_editor_state(indoc! {"
17215        fn func(abc ˇdef: i32) -> u32 {
17216        }
17217    "});
17218
17219    cx.update_editor(|editor, window, cx| {
17220        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17221    });
17222
17223    cx.assert_editor_state(indoc! {"
17224        fn func(abcˇ def: i32) -> u32 {
17225        }
17226    "});
17227
17228    cx.update_editor(|editor, window, cx| {
17229        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17230    });
17231
17232    cx.assert_editor_state(indoc! {"
17233        fn func(abc def: i32) -> ˇu32 {
17234        }
17235    "});
17236}
17237
17238#[gpui::test]
17239async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17240    init_test(cx, |_| {});
17241
17242    let mut cx = EditorTestContext::new(cx).await;
17243
17244    let diff_base = r#"
17245        use some::mod;
17246
17247        const A: u32 = 42;
17248
17249        fn main() {
17250            println!("hello");
17251
17252            println!("world");
17253        }
17254        "#
17255    .unindent();
17256
17257    // Edits are modified, removed, modified, added
17258    cx.set_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.set_head_text(&diff_base);
17274    executor.run_until_parked();
17275
17276    cx.update_editor(|editor, window, cx| {
17277        //Wrap around the bottom of the buffer
17278        for _ in 0..3 {
17279            editor.go_to_next_hunk(&GoToHunk, window, cx);
17280        }
17281    });
17282
17283    cx.assert_editor_state(
17284        &r#"
17285        ˇuse some::modified;
17286
17287
17288        fn main() {
17289            println!("hello there");
17290
17291            println!("around the");
17292            println!("world");
17293        }
17294        "#
17295        .unindent(),
17296    );
17297
17298    cx.update_editor(|editor, window, cx| {
17299        //Wrap around the top of the buffer
17300        for _ in 0..2 {
17301            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17302        }
17303    });
17304
17305    cx.assert_editor_state(
17306        &r#"
17307        use some::modified;
17308
17309
17310        fn main() {
17311        ˇ    println!("hello there");
17312
17313            println!("around the");
17314            println!("world");
17315        }
17316        "#
17317        .unindent(),
17318    );
17319
17320    cx.update_editor(|editor, window, cx| {
17321        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17322    });
17323
17324    cx.assert_editor_state(
17325        &r#"
17326        use some::modified;
17327
17328        ˇ
17329        fn main() {
17330            println!("hello there");
17331
17332            println!("around the");
17333            println!("world");
17334        }
17335        "#
17336        .unindent(),
17337    );
17338
17339    cx.update_editor(|editor, window, cx| {
17340        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17341    });
17342
17343    cx.assert_editor_state(
17344        &r#"
17345        ˇuse some::modified;
17346
17347
17348        fn main() {
17349            println!("hello there");
17350
17351            println!("around the");
17352            println!("world");
17353        }
17354        "#
17355        .unindent(),
17356    );
17357
17358    cx.update_editor(|editor, window, cx| {
17359        for _ in 0..2 {
17360            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17361        }
17362    });
17363
17364    cx.assert_editor_state(
17365        &r#"
17366        use some::modified;
17367
17368
17369        fn main() {
17370        ˇ    println!("hello there");
17371
17372            println!("around the");
17373            println!("world");
17374        }
17375        "#
17376        .unindent(),
17377    );
17378
17379    cx.update_editor(|editor, window, cx| {
17380        editor.fold(&Fold, window, cx);
17381    });
17382
17383    cx.update_editor(|editor, window, cx| {
17384        editor.go_to_next_hunk(&GoToHunk, window, cx);
17385    });
17386
17387    cx.assert_editor_state(
17388        &r#"
17389        ˇuse some::modified;
17390
17391
17392        fn main() {
17393            println!("hello there");
17394
17395            println!("around the");
17396            println!("world");
17397        }
17398        "#
17399        .unindent(),
17400    );
17401}
17402
17403#[test]
17404fn test_split_words() {
17405    fn split(text: &str) -> Vec<&str> {
17406        split_words(text).collect()
17407    }
17408
17409    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
17410    assert_eq!(split("hello_world"), &["hello_", "world"]);
17411    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
17412    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
17413    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
17414    assert_eq!(split("helloworld"), &["helloworld"]);
17415
17416    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
17417}
17418
17419#[test]
17420fn test_split_words_for_snippet_prefix() {
17421    fn split(text: &str) -> Vec<&str> {
17422        snippet_candidate_suffixes(text, |c| c.is_alphanumeric() || c == '_').collect()
17423    }
17424
17425    assert_eq!(split("HelloWorld"), &["HelloWorld"]);
17426    assert_eq!(split("hello_world"), &["hello_world"]);
17427    assert_eq!(split("_hello_world_"), &["_hello_world_"]);
17428    assert_eq!(split("Hello_World"), &["Hello_World"]);
17429    assert_eq!(split("helloWOrld"), &["helloWOrld"]);
17430    assert_eq!(split("helloworld"), &["helloworld"]);
17431    assert_eq!(
17432        split("this@is!@#$^many   . symbols"),
17433        &[
17434            "symbols",
17435            " symbols",
17436            ". symbols",
17437            " . symbols",
17438            "  . symbols",
17439            "   . symbols",
17440            "many   . symbols",
17441            "^many   . symbols",
17442            "$^many   . symbols",
17443            "#$^many   . symbols",
17444            "@#$^many   . symbols",
17445            "!@#$^many   . symbols",
17446            "is!@#$^many   . symbols",
17447            "@is!@#$^many   . symbols",
17448            "this@is!@#$^many   . symbols",
17449        ],
17450    );
17451    assert_eq!(split("a.s"), &["s", ".s", "a.s"]);
17452}
17453
17454#[gpui::test]
17455async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
17456    init_test(cx, |_| {});
17457
17458    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
17459    let mut assert = |before, after| {
17460        let _state_context = cx.set_state(before);
17461        cx.run_until_parked();
17462        cx.update_editor(|editor, window, cx| {
17463            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
17464        });
17465        cx.run_until_parked();
17466        cx.assert_editor_state(after);
17467    };
17468
17469    // Outside bracket jumps to outside of matching bracket
17470    assert("console.logˇ(var);", "console.log(var)ˇ;");
17471    assert("console.log(var)ˇ;", "console.logˇ(var);");
17472
17473    // Inside bracket jumps to inside of matching bracket
17474    assert("console.log(ˇvar);", "console.log(varˇ);");
17475    assert("console.log(varˇ);", "console.log(ˇvar);");
17476
17477    // When outside a bracket and inside, favor jumping to the inside bracket
17478    assert(
17479        "console.log('foo', [1, 2, 3]ˇ);",
17480        "console.log(ˇ'foo', [1, 2, 3]);",
17481    );
17482    assert(
17483        "console.log(ˇ'foo', [1, 2, 3]);",
17484        "console.log('foo', [1, 2, 3]ˇ);",
17485    );
17486
17487    // Bias forward if two options are equally likely
17488    assert(
17489        "let result = curried_fun()ˇ();",
17490        "let result = curried_fun()()ˇ;",
17491    );
17492
17493    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
17494    assert(
17495        indoc! {"
17496            function test() {
17497                console.log('test')ˇ
17498            }"},
17499        indoc! {"
17500            function test() {
17501                console.logˇ('test')
17502            }"},
17503    );
17504}
17505
17506#[gpui::test]
17507async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
17508    init_test(cx, |_| {});
17509
17510    let fs = FakeFs::new(cx.executor());
17511    fs.insert_tree(
17512        path!("/a"),
17513        json!({
17514            "main.rs": "fn main() { let a = 5; }",
17515            "other.rs": "// Test file",
17516        }),
17517    )
17518    .await;
17519    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17520
17521    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17522    language_registry.add(Arc::new(Language::new(
17523        LanguageConfig {
17524            name: "Rust".into(),
17525            matcher: LanguageMatcher {
17526                path_suffixes: vec!["rs".to_string()],
17527                ..Default::default()
17528            },
17529            brackets: BracketPairConfig {
17530                pairs: vec![BracketPair {
17531                    start: "{".to_string(),
17532                    end: "}".to_string(),
17533                    close: true,
17534                    surround: true,
17535                    newline: true,
17536                }],
17537                disabled_scopes_by_bracket_ix: Vec::new(),
17538            },
17539            ..Default::default()
17540        },
17541        Some(tree_sitter_rust::LANGUAGE.into()),
17542    )));
17543    let mut fake_servers = language_registry.register_fake_lsp(
17544        "Rust",
17545        FakeLspAdapter {
17546            capabilities: lsp::ServerCapabilities {
17547                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17548                    first_trigger_character: "{".to_string(),
17549                    more_trigger_character: None,
17550                }),
17551                ..Default::default()
17552            },
17553            ..Default::default()
17554        },
17555    );
17556
17557    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17558
17559    let cx = &mut VisualTestContext::from_window(*workspace, cx);
17560
17561    let worktree_id = workspace
17562        .update(cx, |workspace, _, cx| {
17563            workspace.project().update(cx, |project, cx| {
17564                project.worktrees(cx).next().unwrap().read(cx).id()
17565            })
17566        })
17567        .unwrap();
17568
17569    let buffer = project
17570        .update(cx, |project, cx| {
17571            project.open_local_buffer(path!("/a/main.rs"), cx)
17572        })
17573        .await
17574        .unwrap();
17575    let editor_handle = workspace
17576        .update(cx, |workspace, window, cx| {
17577            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
17578        })
17579        .unwrap()
17580        .await
17581        .unwrap()
17582        .downcast::<Editor>()
17583        .unwrap();
17584
17585    cx.executor().start_waiting();
17586    let fake_server = fake_servers.next().await.unwrap();
17587
17588    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
17589        |params, _| async move {
17590            assert_eq!(
17591                params.text_document_position.text_document.uri,
17592                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
17593            );
17594            assert_eq!(
17595                params.text_document_position.position,
17596                lsp::Position::new(0, 21),
17597            );
17598
17599            Ok(Some(vec![lsp::TextEdit {
17600                new_text: "]".to_string(),
17601                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17602            }]))
17603        },
17604    );
17605
17606    editor_handle.update_in(cx, |editor, window, cx| {
17607        window.focus(&editor.focus_handle(cx));
17608        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17609            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
17610        });
17611        editor.handle_input("{", window, cx);
17612    });
17613
17614    cx.executor().run_until_parked();
17615
17616    buffer.update(cx, |buffer, _| {
17617        assert_eq!(
17618            buffer.text(),
17619            "fn main() { let a = {5}; }",
17620            "No extra braces from on type formatting should appear in the buffer"
17621        )
17622    });
17623}
17624
17625#[gpui::test(iterations = 20, seeds(31))]
17626async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
17627    init_test(cx, |_| {});
17628
17629    let mut cx = EditorLspTestContext::new_rust(
17630        lsp::ServerCapabilities {
17631            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17632                first_trigger_character: ".".to_string(),
17633                more_trigger_character: None,
17634            }),
17635            ..Default::default()
17636        },
17637        cx,
17638    )
17639    .await;
17640
17641    cx.update_buffer(|buffer, _| {
17642        // This causes autoindent to be async.
17643        buffer.set_sync_parse_timeout(Duration::ZERO)
17644    });
17645
17646    cx.set_state("fn c() {\n    d()ˇ\n}\n");
17647    cx.simulate_keystroke("\n");
17648    cx.run_until_parked();
17649
17650    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
17651    let mut request =
17652        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
17653            let buffer_cloned = buffer_cloned.clone();
17654            async move {
17655                buffer_cloned.update(&mut cx, |buffer, _| {
17656                    assert_eq!(
17657                        buffer.text(),
17658                        "fn c() {\n    d()\n        .\n}\n",
17659                        "OnTypeFormatting should triggered after autoindent applied"
17660                    )
17661                })?;
17662
17663                Ok(Some(vec![]))
17664            }
17665        });
17666
17667    cx.simulate_keystroke(".");
17668    cx.run_until_parked();
17669
17670    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
17671    assert!(request.next().await.is_some());
17672    request.close();
17673    assert!(request.next().await.is_none());
17674}
17675
17676#[gpui::test]
17677async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
17678    init_test(cx, |_| {});
17679
17680    let fs = FakeFs::new(cx.executor());
17681    fs.insert_tree(
17682        path!("/a"),
17683        json!({
17684            "main.rs": "fn main() { let a = 5; }",
17685            "other.rs": "// Test file",
17686        }),
17687    )
17688    .await;
17689
17690    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17691
17692    let server_restarts = Arc::new(AtomicUsize::new(0));
17693    let closure_restarts = Arc::clone(&server_restarts);
17694    let language_server_name = "test language server";
17695    let language_name: LanguageName = "Rust".into();
17696
17697    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17698    language_registry.add(Arc::new(Language::new(
17699        LanguageConfig {
17700            name: language_name.clone(),
17701            matcher: LanguageMatcher {
17702                path_suffixes: vec!["rs".to_string()],
17703                ..Default::default()
17704            },
17705            ..Default::default()
17706        },
17707        Some(tree_sitter_rust::LANGUAGE.into()),
17708    )));
17709    let mut fake_servers = language_registry.register_fake_lsp(
17710        "Rust",
17711        FakeLspAdapter {
17712            name: language_server_name,
17713            initialization_options: Some(json!({
17714                "testOptionValue": true
17715            })),
17716            initializer: Some(Box::new(move |fake_server| {
17717                let task_restarts = Arc::clone(&closure_restarts);
17718                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
17719                    task_restarts.fetch_add(1, atomic::Ordering::Release);
17720                    futures::future::ready(Ok(()))
17721                });
17722            })),
17723            ..Default::default()
17724        },
17725    );
17726
17727    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17728    let _buffer = project
17729        .update(cx, |project, cx| {
17730            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
17731        })
17732        .await
17733        .unwrap();
17734    let _fake_server = fake_servers.next().await.unwrap();
17735    update_test_language_settings(cx, |language_settings| {
17736        language_settings.languages.0.insert(
17737            language_name.clone().0,
17738            LanguageSettingsContent {
17739                tab_size: NonZeroU32::new(8),
17740                ..Default::default()
17741            },
17742        );
17743    });
17744    cx.executor().run_until_parked();
17745    assert_eq!(
17746        server_restarts.load(atomic::Ordering::Acquire),
17747        0,
17748        "Should not restart LSP server on an unrelated change"
17749    );
17750
17751    update_test_project_settings(cx, |project_settings| {
17752        project_settings.lsp.insert(
17753            "Some other server name".into(),
17754            LspSettings {
17755                binary: None,
17756                settings: None,
17757                initialization_options: Some(json!({
17758                    "some other init value": false
17759                })),
17760                enable_lsp_tasks: false,
17761                fetch: None,
17762            },
17763        );
17764    });
17765    cx.executor().run_until_parked();
17766    assert_eq!(
17767        server_restarts.load(atomic::Ordering::Acquire),
17768        0,
17769        "Should not restart LSP server on an unrelated LSP settings change"
17770    );
17771
17772    update_test_project_settings(cx, |project_settings| {
17773        project_settings.lsp.insert(
17774            language_server_name.into(),
17775            LspSettings {
17776                binary: None,
17777                settings: None,
17778                initialization_options: Some(json!({
17779                    "anotherInitValue": false
17780                })),
17781                enable_lsp_tasks: false,
17782                fetch: None,
17783            },
17784        );
17785    });
17786    cx.executor().run_until_parked();
17787    assert_eq!(
17788        server_restarts.load(atomic::Ordering::Acquire),
17789        1,
17790        "Should restart LSP server on a related LSP settings change"
17791    );
17792
17793    update_test_project_settings(cx, |project_settings| {
17794        project_settings.lsp.insert(
17795            language_server_name.into(),
17796            LspSettings {
17797                binary: None,
17798                settings: None,
17799                initialization_options: Some(json!({
17800                    "anotherInitValue": false
17801                })),
17802                enable_lsp_tasks: false,
17803                fetch: None,
17804            },
17805        );
17806    });
17807    cx.executor().run_until_parked();
17808    assert_eq!(
17809        server_restarts.load(atomic::Ordering::Acquire),
17810        1,
17811        "Should not restart LSP server on a related LSP settings change that is the same"
17812    );
17813
17814    update_test_project_settings(cx, |project_settings| {
17815        project_settings.lsp.insert(
17816            language_server_name.into(),
17817            LspSettings {
17818                binary: None,
17819                settings: None,
17820                initialization_options: None,
17821                enable_lsp_tasks: false,
17822                fetch: None,
17823            },
17824        );
17825    });
17826    cx.executor().run_until_parked();
17827    assert_eq!(
17828        server_restarts.load(atomic::Ordering::Acquire),
17829        2,
17830        "Should restart LSP server on another related LSP settings change"
17831    );
17832}
17833
17834#[gpui::test]
17835async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
17836    init_test(cx, |_| {});
17837
17838    let mut cx = EditorLspTestContext::new_rust(
17839        lsp::ServerCapabilities {
17840            completion_provider: Some(lsp::CompletionOptions {
17841                trigger_characters: Some(vec![".".to_string()]),
17842                resolve_provider: Some(true),
17843                ..Default::default()
17844            }),
17845            ..Default::default()
17846        },
17847        cx,
17848    )
17849    .await;
17850
17851    cx.set_state("fn main() { let a = 2ˇ; }");
17852    cx.simulate_keystroke(".");
17853    let completion_item = lsp::CompletionItem {
17854        label: "some".into(),
17855        kind: Some(lsp::CompletionItemKind::SNIPPET),
17856        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
17857        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
17858            kind: lsp::MarkupKind::Markdown,
17859            value: "```rust\nSome(2)\n```".to_string(),
17860        })),
17861        deprecated: Some(false),
17862        sort_text: Some("fffffff2".to_string()),
17863        filter_text: Some("some".to_string()),
17864        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
17865        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17866            range: lsp::Range {
17867                start: lsp::Position {
17868                    line: 0,
17869                    character: 22,
17870                },
17871                end: lsp::Position {
17872                    line: 0,
17873                    character: 22,
17874                },
17875            },
17876            new_text: "Some(2)".to_string(),
17877        })),
17878        additional_text_edits: Some(vec![lsp::TextEdit {
17879            range: lsp::Range {
17880                start: lsp::Position {
17881                    line: 0,
17882                    character: 20,
17883                },
17884                end: lsp::Position {
17885                    line: 0,
17886                    character: 22,
17887                },
17888            },
17889            new_text: "".to_string(),
17890        }]),
17891        ..Default::default()
17892    };
17893
17894    let closure_completion_item = completion_item.clone();
17895    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17896        let task_completion_item = closure_completion_item.clone();
17897        async move {
17898            Ok(Some(lsp::CompletionResponse::Array(vec![
17899                task_completion_item,
17900            ])))
17901        }
17902    });
17903
17904    request.next().await;
17905
17906    cx.condition(|editor, _| editor.context_menu_visible())
17907        .await;
17908    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
17909        editor
17910            .confirm_completion(&ConfirmCompletion::default(), window, cx)
17911            .unwrap()
17912    });
17913    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
17914
17915    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
17916        let task_completion_item = completion_item.clone();
17917        async move { Ok(task_completion_item) }
17918    })
17919    .next()
17920    .await
17921    .unwrap();
17922    apply_additional_edits.await.unwrap();
17923    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
17924}
17925
17926#[gpui::test]
17927async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
17928    init_test(cx, |_| {});
17929
17930    let mut cx = EditorLspTestContext::new_rust(
17931        lsp::ServerCapabilities {
17932            completion_provider: Some(lsp::CompletionOptions {
17933                trigger_characters: Some(vec![".".to_string()]),
17934                resolve_provider: Some(true),
17935                ..Default::default()
17936            }),
17937            ..Default::default()
17938        },
17939        cx,
17940    )
17941    .await;
17942
17943    cx.set_state("fn main() { let a = 2ˇ; }");
17944    cx.simulate_keystroke(".");
17945
17946    let item1 = lsp::CompletionItem {
17947        label: "method id()".to_string(),
17948        filter_text: Some("id".to_string()),
17949        detail: None,
17950        documentation: None,
17951        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17952            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17953            new_text: ".id".to_string(),
17954        })),
17955        ..lsp::CompletionItem::default()
17956    };
17957
17958    let item2 = lsp::CompletionItem {
17959        label: "other".to_string(),
17960        filter_text: Some("other".to_string()),
17961        detail: None,
17962        documentation: None,
17963        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17964            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17965            new_text: ".other".to_string(),
17966        })),
17967        ..lsp::CompletionItem::default()
17968    };
17969
17970    let item1 = item1.clone();
17971    cx.set_request_handler::<lsp::request::Completion, _, _>({
17972        let item1 = item1.clone();
17973        move |_, _, _| {
17974            let item1 = item1.clone();
17975            let item2 = item2.clone();
17976            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
17977        }
17978    })
17979    .next()
17980    .await;
17981
17982    cx.condition(|editor, _| editor.context_menu_visible())
17983        .await;
17984    cx.update_editor(|editor, _, _| {
17985        let context_menu = editor.context_menu.borrow_mut();
17986        let context_menu = context_menu
17987            .as_ref()
17988            .expect("Should have the context menu deployed");
17989        match context_menu {
17990            CodeContextMenu::Completions(completions_menu) => {
17991                let completions = completions_menu.completions.borrow_mut();
17992                assert_eq!(
17993                    completions
17994                        .iter()
17995                        .map(|completion| &completion.label.text)
17996                        .collect::<Vec<_>>(),
17997                    vec!["method id()", "other"]
17998                )
17999            }
18000            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18001        }
18002    });
18003
18004    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
18005        let item1 = item1.clone();
18006        move |_, item_to_resolve, _| {
18007            let item1 = item1.clone();
18008            async move {
18009                if item1 == item_to_resolve {
18010                    Ok(lsp::CompletionItem {
18011                        label: "method id()".to_string(),
18012                        filter_text: Some("id".to_string()),
18013                        detail: Some("Now resolved!".to_string()),
18014                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
18015                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18016                            range: lsp::Range::new(
18017                                lsp::Position::new(0, 22),
18018                                lsp::Position::new(0, 22),
18019                            ),
18020                            new_text: ".id".to_string(),
18021                        })),
18022                        ..lsp::CompletionItem::default()
18023                    })
18024                } else {
18025                    Ok(item_to_resolve)
18026                }
18027            }
18028        }
18029    })
18030    .next()
18031    .await
18032    .unwrap();
18033    cx.run_until_parked();
18034
18035    cx.update_editor(|editor, window, cx| {
18036        editor.context_menu_next(&Default::default(), window, cx);
18037    });
18038
18039    cx.update_editor(|editor, _, _| {
18040        let context_menu = editor.context_menu.borrow_mut();
18041        let context_menu = context_menu
18042            .as_ref()
18043            .expect("Should have the context menu deployed");
18044        match context_menu {
18045            CodeContextMenu::Completions(completions_menu) => {
18046                let completions = completions_menu.completions.borrow_mut();
18047                assert_eq!(
18048                    completions
18049                        .iter()
18050                        .map(|completion| &completion.label.text)
18051                        .collect::<Vec<_>>(),
18052                    vec!["method id() Now resolved!", "other"],
18053                    "Should update first completion label, but not second as the filter text did not match."
18054                );
18055            }
18056            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18057        }
18058    });
18059}
18060
18061#[gpui::test]
18062async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
18063    init_test(cx, |_| {});
18064    let mut cx = EditorLspTestContext::new_rust(
18065        lsp::ServerCapabilities {
18066            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
18067            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
18068            completion_provider: Some(lsp::CompletionOptions {
18069                resolve_provider: Some(true),
18070                ..Default::default()
18071            }),
18072            ..Default::default()
18073        },
18074        cx,
18075    )
18076    .await;
18077    cx.set_state(indoc! {"
18078        struct TestStruct {
18079            field: i32
18080        }
18081
18082        fn mainˇ() {
18083            let unused_var = 42;
18084            let test_struct = TestStruct { field: 42 };
18085        }
18086    "});
18087    let symbol_range = cx.lsp_range(indoc! {"
18088        struct TestStruct {
18089            field: i32
18090        }
18091
18092        «fn main»() {
18093            let unused_var = 42;
18094            let test_struct = TestStruct { field: 42 };
18095        }
18096    "});
18097    let mut hover_requests =
18098        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
18099            Ok(Some(lsp::Hover {
18100                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
18101                    kind: lsp::MarkupKind::Markdown,
18102                    value: "Function documentation".to_string(),
18103                }),
18104                range: Some(symbol_range),
18105            }))
18106        });
18107
18108    // Case 1: Test that code action menu hide hover popover
18109    cx.dispatch_action(Hover);
18110    hover_requests.next().await;
18111    cx.condition(|editor, _| editor.hover_state.visible()).await;
18112    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
18113        move |_, _, _| async move {
18114            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
18115                lsp::CodeAction {
18116                    title: "Remove unused variable".to_string(),
18117                    kind: Some(CodeActionKind::QUICKFIX),
18118                    edit: Some(lsp::WorkspaceEdit {
18119                        changes: Some(
18120                            [(
18121                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
18122                                vec![lsp::TextEdit {
18123                                    range: lsp::Range::new(
18124                                        lsp::Position::new(5, 4),
18125                                        lsp::Position::new(5, 27),
18126                                    ),
18127                                    new_text: "".to_string(),
18128                                }],
18129                            )]
18130                            .into_iter()
18131                            .collect(),
18132                        ),
18133                        ..Default::default()
18134                    }),
18135                    ..Default::default()
18136                },
18137            )]))
18138        },
18139    );
18140    cx.update_editor(|editor, window, cx| {
18141        editor.toggle_code_actions(
18142            &ToggleCodeActions {
18143                deployed_from: None,
18144                quick_launch: false,
18145            },
18146            window,
18147            cx,
18148        );
18149    });
18150    code_action_requests.next().await;
18151    cx.run_until_parked();
18152    cx.condition(|editor, _| editor.context_menu_visible())
18153        .await;
18154    cx.update_editor(|editor, _, _| {
18155        assert!(
18156            !editor.hover_state.visible(),
18157            "Hover popover should be hidden when code action menu is shown"
18158        );
18159        // Hide code actions
18160        editor.context_menu.take();
18161    });
18162
18163    // Case 2: Test that code completions hide hover popover
18164    cx.dispatch_action(Hover);
18165    hover_requests.next().await;
18166    cx.condition(|editor, _| editor.hover_state.visible()).await;
18167    let counter = Arc::new(AtomicUsize::new(0));
18168    let mut completion_requests =
18169        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18170            let counter = counter.clone();
18171            async move {
18172                counter.fetch_add(1, atomic::Ordering::Release);
18173                Ok(Some(lsp::CompletionResponse::Array(vec![
18174                    lsp::CompletionItem {
18175                        label: "main".into(),
18176                        kind: Some(lsp::CompletionItemKind::FUNCTION),
18177                        detail: Some("() -> ()".to_string()),
18178                        ..Default::default()
18179                    },
18180                    lsp::CompletionItem {
18181                        label: "TestStruct".into(),
18182                        kind: Some(lsp::CompletionItemKind::STRUCT),
18183                        detail: Some("struct TestStruct".to_string()),
18184                        ..Default::default()
18185                    },
18186                ])))
18187            }
18188        });
18189    cx.update_editor(|editor, window, cx| {
18190        editor.show_completions(&ShowCompletions, window, cx);
18191    });
18192    completion_requests.next().await;
18193    cx.condition(|editor, _| editor.context_menu_visible())
18194        .await;
18195    cx.update_editor(|editor, _, _| {
18196        assert!(
18197            !editor.hover_state.visible(),
18198            "Hover popover should be hidden when completion menu is shown"
18199        );
18200    });
18201}
18202
18203#[gpui::test]
18204async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
18205    init_test(cx, |_| {});
18206
18207    let mut cx = EditorLspTestContext::new_rust(
18208        lsp::ServerCapabilities {
18209            completion_provider: Some(lsp::CompletionOptions {
18210                trigger_characters: Some(vec![".".to_string()]),
18211                resolve_provider: Some(true),
18212                ..Default::default()
18213            }),
18214            ..Default::default()
18215        },
18216        cx,
18217    )
18218    .await;
18219
18220    cx.set_state("fn main() { let a = 2ˇ; }");
18221    cx.simulate_keystroke(".");
18222
18223    let unresolved_item_1 = lsp::CompletionItem {
18224        label: "id".to_string(),
18225        filter_text: Some("id".to_string()),
18226        detail: None,
18227        documentation: None,
18228        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18229            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18230            new_text: ".id".to_string(),
18231        })),
18232        ..lsp::CompletionItem::default()
18233    };
18234    let resolved_item_1 = lsp::CompletionItem {
18235        additional_text_edits: Some(vec![lsp::TextEdit {
18236            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
18237            new_text: "!!".to_string(),
18238        }]),
18239        ..unresolved_item_1.clone()
18240    };
18241    let unresolved_item_2 = lsp::CompletionItem {
18242        label: "other".to_string(),
18243        filter_text: Some("other".to_string()),
18244        detail: None,
18245        documentation: None,
18246        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18247            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18248            new_text: ".other".to_string(),
18249        })),
18250        ..lsp::CompletionItem::default()
18251    };
18252    let resolved_item_2 = lsp::CompletionItem {
18253        additional_text_edits: Some(vec![lsp::TextEdit {
18254            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
18255            new_text: "??".to_string(),
18256        }]),
18257        ..unresolved_item_2.clone()
18258    };
18259
18260    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
18261    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
18262    cx.lsp
18263        .server
18264        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
18265            let unresolved_item_1 = unresolved_item_1.clone();
18266            let resolved_item_1 = resolved_item_1.clone();
18267            let unresolved_item_2 = unresolved_item_2.clone();
18268            let resolved_item_2 = resolved_item_2.clone();
18269            let resolve_requests_1 = resolve_requests_1.clone();
18270            let resolve_requests_2 = resolve_requests_2.clone();
18271            move |unresolved_request, _| {
18272                let unresolved_item_1 = unresolved_item_1.clone();
18273                let resolved_item_1 = resolved_item_1.clone();
18274                let unresolved_item_2 = unresolved_item_2.clone();
18275                let resolved_item_2 = resolved_item_2.clone();
18276                let resolve_requests_1 = resolve_requests_1.clone();
18277                let resolve_requests_2 = resolve_requests_2.clone();
18278                async move {
18279                    if unresolved_request == unresolved_item_1 {
18280                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
18281                        Ok(resolved_item_1.clone())
18282                    } else if unresolved_request == unresolved_item_2 {
18283                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
18284                        Ok(resolved_item_2.clone())
18285                    } else {
18286                        panic!("Unexpected completion item {unresolved_request:?}")
18287                    }
18288                }
18289            }
18290        })
18291        .detach();
18292
18293    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18294        let unresolved_item_1 = unresolved_item_1.clone();
18295        let unresolved_item_2 = unresolved_item_2.clone();
18296        async move {
18297            Ok(Some(lsp::CompletionResponse::Array(vec![
18298                unresolved_item_1,
18299                unresolved_item_2,
18300            ])))
18301        }
18302    })
18303    .next()
18304    .await;
18305
18306    cx.condition(|editor, _| editor.context_menu_visible())
18307        .await;
18308    cx.update_editor(|editor, _, _| {
18309        let context_menu = editor.context_menu.borrow_mut();
18310        let context_menu = context_menu
18311            .as_ref()
18312            .expect("Should have the context menu deployed");
18313        match context_menu {
18314            CodeContextMenu::Completions(completions_menu) => {
18315                let completions = completions_menu.completions.borrow_mut();
18316                assert_eq!(
18317                    completions
18318                        .iter()
18319                        .map(|completion| &completion.label.text)
18320                        .collect::<Vec<_>>(),
18321                    vec!["id", "other"]
18322                )
18323            }
18324            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18325        }
18326    });
18327    cx.run_until_parked();
18328
18329    cx.update_editor(|editor, window, cx| {
18330        editor.context_menu_next(&ContextMenuNext, window, cx);
18331    });
18332    cx.run_until_parked();
18333    cx.update_editor(|editor, window, cx| {
18334        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
18335    });
18336    cx.run_until_parked();
18337    cx.update_editor(|editor, window, cx| {
18338        editor.context_menu_next(&ContextMenuNext, window, cx);
18339    });
18340    cx.run_until_parked();
18341    cx.update_editor(|editor, window, cx| {
18342        editor
18343            .compose_completion(&ComposeCompletion::default(), window, cx)
18344            .expect("No task returned")
18345    })
18346    .await
18347    .expect("Completion failed");
18348    cx.run_until_parked();
18349
18350    cx.update_editor(|editor, _, cx| {
18351        assert_eq!(
18352            resolve_requests_1.load(atomic::Ordering::Acquire),
18353            1,
18354            "Should always resolve once despite multiple selections"
18355        );
18356        assert_eq!(
18357            resolve_requests_2.load(atomic::Ordering::Acquire),
18358            1,
18359            "Should always resolve once after multiple selections and applying the completion"
18360        );
18361        assert_eq!(
18362            editor.text(cx),
18363            "fn main() { let a = ??.other; }",
18364            "Should use resolved data when applying the completion"
18365        );
18366    });
18367}
18368
18369#[gpui::test]
18370async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
18371    init_test(cx, |_| {});
18372
18373    let item_0 = lsp::CompletionItem {
18374        label: "abs".into(),
18375        insert_text: Some("abs".into()),
18376        data: Some(json!({ "very": "special"})),
18377        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
18378        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
18379            lsp::InsertReplaceEdit {
18380                new_text: "abs".to_string(),
18381                insert: lsp::Range::default(),
18382                replace: lsp::Range::default(),
18383            },
18384        )),
18385        ..lsp::CompletionItem::default()
18386    };
18387    let items = iter::once(item_0.clone())
18388        .chain((11..51).map(|i| lsp::CompletionItem {
18389            label: format!("item_{}", i),
18390            insert_text: Some(format!("item_{}", i)),
18391            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
18392            ..lsp::CompletionItem::default()
18393        }))
18394        .collect::<Vec<_>>();
18395
18396    let default_commit_characters = vec!["?".to_string()];
18397    let default_data = json!({ "default": "data"});
18398    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
18399    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
18400    let default_edit_range = lsp::Range {
18401        start: lsp::Position {
18402            line: 0,
18403            character: 5,
18404        },
18405        end: lsp::Position {
18406            line: 0,
18407            character: 5,
18408        },
18409    };
18410
18411    let mut cx = EditorLspTestContext::new_rust(
18412        lsp::ServerCapabilities {
18413            completion_provider: Some(lsp::CompletionOptions {
18414                trigger_characters: Some(vec![".".to_string()]),
18415                resolve_provider: Some(true),
18416                ..Default::default()
18417            }),
18418            ..Default::default()
18419        },
18420        cx,
18421    )
18422    .await;
18423
18424    cx.set_state("fn main() { let a = 2ˇ; }");
18425    cx.simulate_keystroke(".");
18426
18427    let completion_data = default_data.clone();
18428    let completion_characters = default_commit_characters.clone();
18429    let completion_items = items.clone();
18430    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18431        let default_data = completion_data.clone();
18432        let default_commit_characters = completion_characters.clone();
18433        let items = completion_items.clone();
18434        async move {
18435            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
18436                items,
18437                item_defaults: Some(lsp::CompletionListItemDefaults {
18438                    data: Some(default_data.clone()),
18439                    commit_characters: Some(default_commit_characters.clone()),
18440                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
18441                        default_edit_range,
18442                    )),
18443                    insert_text_format: Some(default_insert_text_format),
18444                    insert_text_mode: Some(default_insert_text_mode),
18445                }),
18446                ..lsp::CompletionList::default()
18447            })))
18448        }
18449    })
18450    .next()
18451    .await;
18452
18453    let resolved_items = Arc::new(Mutex::new(Vec::new()));
18454    cx.lsp
18455        .server
18456        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
18457            let closure_resolved_items = resolved_items.clone();
18458            move |item_to_resolve, _| {
18459                let closure_resolved_items = closure_resolved_items.clone();
18460                async move {
18461                    closure_resolved_items.lock().push(item_to_resolve.clone());
18462                    Ok(item_to_resolve)
18463                }
18464            }
18465        })
18466        .detach();
18467
18468    cx.condition(|editor, _| editor.context_menu_visible())
18469        .await;
18470    cx.run_until_parked();
18471    cx.update_editor(|editor, _, _| {
18472        let menu = editor.context_menu.borrow_mut();
18473        match menu.as_ref().expect("should have the completions menu") {
18474            CodeContextMenu::Completions(completions_menu) => {
18475                assert_eq!(
18476                    completions_menu
18477                        .entries
18478                        .borrow()
18479                        .iter()
18480                        .map(|mat| mat.string.clone())
18481                        .collect::<Vec<String>>(),
18482                    items
18483                        .iter()
18484                        .map(|completion| completion.label.clone())
18485                        .collect::<Vec<String>>()
18486                );
18487            }
18488            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
18489        }
18490    });
18491    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
18492    // with 4 from the end.
18493    assert_eq!(
18494        *resolved_items.lock(),
18495        [&items[0..16], &items[items.len() - 4..items.len()]]
18496            .concat()
18497            .iter()
18498            .cloned()
18499            .map(|mut item| {
18500                if item.data.is_none() {
18501                    item.data = Some(default_data.clone());
18502                }
18503                item
18504            })
18505            .collect::<Vec<lsp::CompletionItem>>(),
18506        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
18507    );
18508    resolved_items.lock().clear();
18509
18510    cx.update_editor(|editor, window, cx| {
18511        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
18512    });
18513    cx.run_until_parked();
18514    // Completions that have already been resolved are skipped.
18515    assert_eq!(
18516        *resolved_items.lock(),
18517        items[items.len() - 17..items.len() - 4]
18518            .iter()
18519            .cloned()
18520            .map(|mut item| {
18521                if item.data.is_none() {
18522                    item.data = Some(default_data.clone());
18523                }
18524                item
18525            })
18526            .collect::<Vec<lsp::CompletionItem>>()
18527    );
18528    resolved_items.lock().clear();
18529}
18530
18531#[gpui::test]
18532async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
18533    init_test(cx, |_| {});
18534
18535    let mut cx = EditorLspTestContext::new(
18536        Language::new(
18537            LanguageConfig {
18538                matcher: LanguageMatcher {
18539                    path_suffixes: vec!["jsx".into()],
18540                    ..Default::default()
18541                },
18542                overrides: [(
18543                    "element".into(),
18544                    LanguageConfigOverride {
18545                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
18546                        ..Default::default()
18547                    },
18548                )]
18549                .into_iter()
18550                .collect(),
18551                ..Default::default()
18552            },
18553            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
18554        )
18555        .with_override_query("(jsx_self_closing_element) @element")
18556        .unwrap(),
18557        lsp::ServerCapabilities {
18558            completion_provider: Some(lsp::CompletionOptions {
18559                trigger_characters: Some(vec![":".to_string()]),
18560                ..Default::default()
18561            }),
18562            ..Default::default()
18563        },
18564        cx,
18565    )
18566    .await;
18567
18568    cx.lsp
18569        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
18570            Ok(Some(lsp::CompletionResponse::Array(vec![
18571                lsp::CompletionItem {
18572                    label: "bg-blue".into(),
18573                    ..Default::default()
18574                },
18575                lsp::CompletionItem {
18576                    label: "bg-red".into(),
18577                    ..Default::default()
18578                },
18579                lsp::CompletionItem {
18580                    label: "bg-yellow".into(),
18581                    ..Default::default()
18582                },
18583            ])))
18584        });
18585
18586    cx.set_state(r#"<p class="bgˇ" />"#);
18587
18588    // Trigger completion when typing a dash, because the dash is an extra
18589    // word character in the 'element' scope, which contains the cursor.
18590    cx.simulate_keystroke("-");
18591    cx.executor().run_until_parked();
18592    cx.update_editor(|editor, _, _| {
18593        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18594        {
18595            assert_eq!(
18596                completion_menu_entries(menu),
18597                &["bg-blue", "bg-red", "bg-yellow"]
18598            );
18599        } else {
18600            panic!("expected completion menu to be open");
18601        }
18602    });
18603
18604    cx.simulate_keystroke("l");
18605    cx.executor().run_until_parked();
18606    cx.update_editor(|editor, _, _| {
18607        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18608        {
18609            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
18610        } else {
18611            panic!("expected completion menu to be open");
18612        }
18613    });
18614
18615    // When filtering completions, consider the character after the '-' to
18616    // be the start of a subword.
18617    cx.set_state(r#"<p class="yelˇ" />"#);
18618    cx.simulate_keystroke("l");
18619    cx.executor().run_until_parked();
18620    cx.update_editor(|editor, _, _| {
18621        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18622        {
18623            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
18624        } else {
18625            panic!("expected completion menu to be open");
18626        }
18627    });
18628}
18629
18630fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
18631    let entries = menu.entries.borrow();
18632    entries.iter().map(|mat| mat.string.clone()).collect()
18633}
18634
18635#[gpui::test]
18636async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
18637    init_test(cx, |settings| {
18638        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
18639    });
18640
18641    let fs = FakeFs::new(cx.executor());
18642    fs.insert_file(path!("/file.ts"), Default::default()).await;
18643
18644    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
18645    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18646
18647    language_registry.add(Arc::new(Language::new(
18648        LanguageConfig {
18649            name: "TypeScript".into(),
18650            matcher: LanguageMatcher {
18651                path_suffixes: vec!["ts".to_string()],
18652                ..Default::default()
18653            },
18654            ..Default::default()
18655        },
18656        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
18657    )));
18658    update_test_language_settings(cx, |settings| {
18659        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
18660    });
18661
18662    let test_plugin = "test_plugin";
18663    let _ = language_registry.register_fake_lsp(
18664        "TypeScript",
18665        FakeLspAdapter {
18666            prettier_plugins: vec![test_plugin],
18667            ..Default::default()
18668        },
18669    );
18670
18671    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
18672    let buffer = project
18673        .update(cx, |project, cx| {
18674            project.open_local_buffer(path!("/file.ts"), cx)
18675        })
18676        .await
18677        .unwrap();
18678
18679    let buffer_text = "one\ntwo\nthree\n";
18680    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
18681    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
18682    editor.update_in(cx, |editor, window, cx| {
18683        editor.set_text(buffer_text, window, cx)
18684    });
18685
18686    editor
18687        .update_in(cx, |editor, window, cx| {
18688            editor.perform_format(
18689                project.clone(),
18690                FormatTrigger::Manual,
18691                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18692                window,
18693                cx,
18694            )
18695        })
18696        .unwrap()
18697        .await;
18698    assert_eq!(
18699        editor.update(cx, |editor, cx| editor.text(cx)),
18700        buffer_text.to_string() + prettier_format_suffix,
18701        "Test prettier formatting was not applied to the original buffer text",
18702    );
18703
18704    update_test_language_settings(cx, |settings| {
18705        settings.defaults.formatter = Some(FormatterList::default())
18706    });
18707    let format = editor.update_in(cx, |editor, window, cx| {
18708        editor.perform_format(
18709            project.clone(),
18710            FormatTrigger::Manual,
18711            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18712            window,
18713            cx,
18714        )
18715    });
18716    format.await.unwrap();
18717    assert_eq!(
18718        editor.update(cx, |editor, cx| editor.text(cx)),
18719        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
18720        "Autoformatting (via test prettier) was not applied to the original buffer text",
18721    );
18722}
18723
18724#[gpui::test]
18725async fn test_addition_reverts(cx: &mut TestAppContext) {
18726    init_test(cx, |_| {});
18727    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18728    let base_text = indoc! {r#"
18729        struct Row;
18730        struct Row1;
18731        struct Row2;
18732
18733        struct Row4;
18734        struct Row5;
18735        struct Row6;
18736
18737        struct Row8;
18738        struct Row9;
18739        struct Row10;"#};
18740
18741    // When addition hunks are not adjacent to carets, no hunk revert is performed
18742    assert_hunk_revert(
18743        indoc! {r#"struct Row;
18744                   struct Row1;
18745                   struct Row1.1;
18746                   struct Row1.2;
18747                   struct Row2;ˇ
18748
18749                   struct Row4;
18750                   struct Row5;
18751                   struct Row6;
18752
18753                   struct Row8;
18754                   ˇstruct Row9;
18755                   struct Row9.1;
18756                   struct Row9.2;
18757                   struct Row9.3;
18758                   struct Row10;"#},
18759        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18760        indoc! {r#"struct Row;
18761                   struct Row1;
18762                   struct Row1.1;
18763                   struct Row1.2;
18764                   struct Row2;ˇ
18765
18766                   struct Row4;
18767                   struct Row5;
18768                   struct Row6;
18769
18770                   struct Row8;
18771                   ˇstruct Row9;
18772                   struct Row9.1;
18773                   struct Row9.2;
18774                   struct Row9.3;
18775                   struct Row10;"#},
18776        base_text,
18777        &mut cx,
18778    );
18779    // Same for selections
18780    assert_hunk_revert(
18781        indoc! {r#"struct Row;
18782                   struct Row1;
18783                   struct Row2;
18784                   struct Row2.1;
18785                   struct Row2.2;
18786                   «ˇ
18787                   struct Row4;
18788                   struct» Row5;
18789                   «struct Row6;
18790                   ˇ»
18791                   struct Row9.1;
18792                   struct Row9.2;
18793                   struct Row9.3;
18794                   struct Row8;
18795                   struct Row9;
18796                   struct Row10;"#},
18797        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18798        indoc! {r#"struct Row;
18799                   struct Row1;
18800                   struct Row2;
18801                   struct Row2.1;
18802                   struct Row2.2;
18803                   «ˇ
18804                   struct Row4;
18805                   struct» Row5;
18806                   «struct Row6;
18807                   ˇ»
18808                   struct Row9.1;
18809                   struct Row9.2;
18810                   struct Row9.3;
18811                   struct Row8;
18812                   struct Row9;
18813                   struct Row10;"#},
18814        base_text,
18815        &mut cx,
18816    );
18817
18818    // When carets and selections intersect the addition hunks, those are reverted.
18819    // Adjacent carets got merged.
18820    assert_hunk_revert(
18821        indoc! {r#"struct Row;
18822                   ˇ// something on the top
18823                   struct Row1;
18824                   struct Row2;
18825                   struct Roˇw3.1;
18826                   struct Row2.2;
18827                   struct Row2.3;ˇ
18828
18829                   struct Row4;
18830                   struct ˇRow5.1;
18831                   struct Row5.2;
18832                   struct «Rowˇ»5.3;
18833                   struct Row5;
18834                   struct Row6;
18835                   ˇ
18836                   struct Row9.1;
18837                   struct «Rowˇ»9.2;
18838                   struct «ˇRow»9.3;
18839                   struct Row8;
18840                   struct Row9;
18841                   «ˇ// something on bottom»
18842                   struct Row10;"#},
18843        vec![
18844            DiffHunkStatusKind::Added,
18845            DiffHunkStatusKind::Added,
18846            DiffHunkStatusKind::Added,
18847            DiffHunkStatusKind::Added,
18848            DiffHunkStatusKind::Added,
18849        ],
18850        indoc! {r#"struct Row;
18851                   ˇstruct Row1;
18852                   struct Row2;
18853                   ˇ
18854                   struct Row4;
18855                   ˇstruct Row5;
18856                   struct Row6;
18857                   ˇ
18858                   ˇstruct Row8;
18859                   struct Row9;
18860                   ˇstruct Row10;"#},
18861        base_text,
18862        &mut cx,
18863    );
18864}
18865
18866#[gpui::test]
18867async fn test_modification_reverts(cx: &mut TestAppContext) {
18868    init_test(cx, |_| {});
18869    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18870    let base_text = indoc! {r#"
18871        struct Row;
18872        struct Row1;
18873        struct Row2;
18874
18875        struct Row4;
18876        struct Row5;
18877        struct Row6;
18878
18879        struct Row8;
18880        struct Row9;
18881        struct Row10;"#};
18882
18883    // Modification hunks behave the same as the addition ones.
18884    assert_hunk_revert(
18885        indoc! {r#"struct Row;
18886                   struct Row1;
18887                   struct Row33;
18888                   ˇ
18889                   struct Row4;
18890                   struct Row5;
18891                   struct Row6;
18892                   ˇ
18893                   struct Row99;
18894                   struct Row9;
18895                   struct Row10;"#},
18896        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18897        indoc! {r#"struct Row;
18898                   struct Row1;
18899                   struct Row33;
18900                   ˇ
18901                   struct Row4;
18902                   struct Row5;
18903                   struct Row6;
18904                   ˇ
18905                   struct Row99;
18906                   struct Row9;
18907                   struct Row10;"#},
18908        base_text,
18909        &mut cx,
18910    );
18911    assert_hunk_revert(
18912        indoc! {r#"struct Row;
18913                   struct Row1;
18914                   struct Row33;
18915                   «ˇ
18916                   struct Row4;
18917                   struct» Row5;
18918                   «struct Row6;
18919                   ˇ»
18920                   struct Row99;
18921                   struct Row9;
18922                   struct Row10;"#},
18923        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18924        indoc! {r#"struct Row;
18925                   struct Row1;
18926                   struct Row33;
18927                   «ˇ
18928                   struct Row4;
18929                   struct» Row5;
18930                   «struct Row6;
18931                   ˇ»
18932                   struct Row99;
18933                   struct Row9;
18934                   struct Row10;"#},
18935        base_text,
18936        &mut cx,
18937    );
18938
18939    assert_hunk_revert(
18940        indoc! {r#"ˇstruct Row1.1;
18941                   struct Row1;
18942                   «ˇstr»uct Row22;
18943
18944                   struct ˇRow44;
18945                   struct Row5;
18946                   struct «Rˇ»ow66;ˇ
18947
18948                   «struˇ»ct Row88;
18949                   struct Row9;
18950                   struct Row1011;ˇ"#},
18951        vec![
18952            DiffHunkStatusKind::Modified,
18953            DiffHunkStatusKind::Modified,
18954            DiffHunkStatusKind::Modified,
18955            DiffHunkStatusKind::Modified,
18956            DiffHunkStatusKind::Modified,
18957            DiffHunkStatusKind::Modified,
18958        ],
18959        indoc! {r#"struct Row;
18960                   ˇstruct Row1;
18961                   struct Row2;
18962                   ˇ
18963                   struct Row4;
18964                   ˇstruct Row5;
18965                   struct Row6;
18966                   ˇ
18967                   struct Row8;
18968                   ˇstruct Row9;
18969                   struct Row10;ˇ"#},
18970        base_text,
18971        &mut cx,
18972    );
18973}
18974
18975#[gpui::test]
18976async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
18977    init_test(cx, |_| {});
18978    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18979    let base_text = indoc! {r#"
18980        one
18981
18982        two
18983        three
18984        "#};
18985
18986    cx.set_head_text(base_text);
18987    cx.set_state("\nˇ\n");
18988    cx.executor().run_until_parked();
18989    cx.update_editor(|editor, _window, cx| {
18990        editor.expand_selected_diff_hunks(cx);
18991    });
18992    cx.executor().run_until_parked();
18993    cx.update_editor(|editor, window, cx| {
18994        editor.backspace(&Default::default(), window, cx);
18995    });
18996    cx.run_until_parked();
18997    cx.assert_state_with_diff(
18998        indoc! {r#"
18999
19000        - two
19001        - threeˇ
19002        +
19003        "#}
19004        .to_string(),
19005    );
19006}
19007
19008#[gpui::test]
19009async fn test_deletion_reverts(cx: &mut TestAppContext) {
19010    init_test(cx, |_| {});
19011    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19012    let base_text = indoc! {r#"struct Row;
19013struct Row1;
19014struct Row2;
19015
19016struct Row4;
19017struct Row5;
19018struct Row6;
19019
19020struct Row8;
19021struct Row9;
19022struct Row10;"#};
19023
19024    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
19025    assert_hunk_revert(
19026        indoc! {r#"struct Row;
19027                   struct Row2;
19028
19029                   ˇstruct Row4;
19030                   struct Row5;
19031                   struct Row6;
19032                   ˇ
19033                   struct Row8;
19034                   struct Row10;"#},
19035        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
19036        indoc! {r#"struct Row;
19037                   struct Row2;
19038
19039                   ˇstruct Row4;
19040                   struct Row5;
19041                   struct Row6;
19042                   ˇ
19043                   struct Row8;
19044                   struct Row10;"#},
19045        base_text,
19046        &mut cx,
19047    );
19048    assert_hunk_revert(
19049        indoc! {r#"struct Row;
19050                   struct Row2;
19051
19052                   «ˇstruct Row4;
19053                   struct» Row5;
19054                   «struct Row6;
19055                   ˇ»
19056                   struct Row8;
19057                   struct Row10;"#},
19058        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
19059        indoc! {r#"struct Row;
19060                   struct Row2;
19061
19062                   «ˇstruct Row4;
19063                   struct» Row5;
19064                   «struct Row6;
19065                   ˇ»
19066                   struct Row8;
19067                   struct Row10;"#},
19068        base_text,
19069        &mut cx,
19070    );
19071
19072    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
19073    assert_hunk_revert(
19074        indoc! {r#"struct Row;
19075                   ˇstruct Row2;
19076
19077                   struct Row4;
19078                   struct Row5;
19079                   struct Row6;
19080
19081                   struct Row8;ˇ
19082                   struct Row10;"#},
19083        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
19084        indoc! {r#"struct Row;
19085                   struct Row1;
19086                   ˇstruct Row2;
19087
19088                   struct Row4;
19089                   struct Row5;
19090                   struct Row6;
19091
19092                   struct Row8;ˇ
19093                   struct Row9;
19094                   struct Row10;"#},
19095        base_text,
19096        &mut cx,
19097    );
19098    assert_hunk_revert(
19099        indoc! {r#"struct Row;
19100                   struct Row2«ˇ;
19101                   struct Row4;
19102                   struct» Row5;
19103                   «struct Row6;
19104
19105                   struct Row8;ˇ»
19106                   struct Row10;"#},
19107        vec![
19108            DiffHunkStatusKind::Deleted,
19109            DiffHunkStatusKind::Deleted,
19110            DiffHunkStatusKind::Deleted,
19111        ],
19112        indoc! {r#"struct Row;
19113                   struct Row1;
19114                   struct Row2«ˇ;
19115
19116                   struct Row4;
19117                   struct» Row5;
19118                   «struct Row6;
19119
19120                   struct Row8;ˇ»
19121                   struct Row9;
19122                   struct Row10;"#},
19123        base_text,
19124        &mut cx,
19125    );
19126}
19127
19128#[gpui::test]
19129async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
19130    init_test(cx, |_| {});
19131
19132    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
19133    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
19134    let base_text_3 =
19135        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
19136
19137    let text_1 = edit_first_char_of_every_line(base_text_1);
19138    let text_2 = edit_first_char_of_every_line(base_text_2);
19139    let text_3 = edit_first_char_of_every_line(base_text_3);
19140
19141    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
19142    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
19143    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
19144
19145    let multibuffer = cx.new(|cx| {
19146        let mut multibuffer = MultiBuffer::new(ReadWrite);
19147        multibuffer.push_excerpts(
19148            buffer_1.clone(),
19149            [
19150                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19151                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19152                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19153            ],
19154            cx,
19155        );
19156        multibuffer.push_excerpts(
19157            buffer_2.clone(),
19158            [
19159                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19160                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19161                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19162            ],
19163            cx,
19164        );
19165        multibuffer.push_excerpts(
19166            buffer_3.clone(),
19167            [
19168                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19169                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19170                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19171            ],
19172            cx,
19173        );
19174        multibuffer
19175    });
19176
19177    let fs = FakeFs::new(cx.executor());
19178    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
19179    let (editor, cx) = cx
19180        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
19181    editor.update_in(cx, |editor, _window, cx| {
19182        for (buffer, diff_base) in [
19183            (buffer_1.clone(), base_text_1),
19184            (buffer_2.clone(), base_text_2),
19185            (buffer_3.clone(), base_text_3),
19186        ] {
19187            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
19188            editor
19189                .buffer
19190                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
19191        }
19192    });
19193    cx.executor().run_until_parked();
19194
19195    editor.update_in(cx, |editor, window, cx| {
19196        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}");
19197        editor.select_all(&SelectAll, window, cx);
19198        editor.git_restore(&Default::default(), window, cx);
19199    });
19200    cx.executor().run_until_parked();
19201
19202    // When all ranges are selected, all buffer hunks are reverted.
19203    editor.update(cx, |editor, cx| {
19204        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");
19205    });
19206    buffer_1.update(cx, |buffer, _| {
19207        assert_eq!(buffer.text(), base_text_1);
19208    });
19209    buffer_2.update(cx, |buffer, _| {
19210        assert_eq!(buffer.text(), base_text_2);
19211    });
19212    buffer_3.update(cx, |buffer, _| {
19213        assert_eq!(buffer.text(), base_text_3);
19214    });
19215
19216    editor.update_in(cx, |editor, window, cx| {
19217        editor.undo(&Default::default(), window, cx);
19218    });
19219
19220    editor.update_in(cx, |editor, window, cx| {
19221        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19222            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
19223        });
19224        editor.git_restore(&Default::default(), window, cx);
19225    });
19226
19227    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
19228    // but not affect buffer_2 and its related excerpts.
19229    editor.update(cx, |editor, cx| {
19230        assert_eq!(
19231            editor.text(cx),
19232            "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}"
19233        );
19234    });
19235    buffer_1.update(cx, |buffer, _| {
19236        assert_eq!(buffer.text(), base_text_1);
19237    });
19238    buffer_2.update(cx, |buffer, _| {
19239        assert_eq!(
19240            buffer.text(),
19241            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
19242        );
19243    });
19244    buffer_3.update(cx, |buffer, _| {
19245        assert_eq!(
19246            buffer.text(),
19247            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
19248        );
19249    });
19250
19251    fn edit_first_char_of_every_line(text: &str) -> String {
19252        text.split('\n')
19253            .map(|line| format!("X{}", &line[1..]))
19254            .collect::<Vec<_>>()
19255            .join("\n")
19256    }
19257}
19258
19259#[gpui::test]
19260async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
19261    init_test(cx, |_| {});
19262
19263    let cols = 4;
19264    let rows = 10;
19265    let sample_text_1 = sample_text(rows, cols, 'a');
19266    assert_eq!(
19267        sample_text_1,
19268        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
19269    );
19270    let sample_text_2 = sample_text(rows, cols, 'l');
19271    assert_eq!(
19272        sample_text_2,
19273        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
19274    );
19275    let sample_text_3 = sample_text(rows, cols, 'v');
19276    assert_eq!(
19277        sample_text_3,
19278        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
19279    );
19280
19281    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
19282    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
19283    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
19284
19285    let multi_buffer = cx.new(|cx| {
19286        let mut multibuffer = MultiBuffer::new(ReadWrite);
19287        multibuffer.push_excerpts(
19288            buffer_1.clone(),
19289            [
19290                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19291                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19292                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19293            ],
19294            cx,
19295        );
19296        multibuffer.push_excerpts(
19297            buffer_2.clone(),
19298            [
19299                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19300                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19301                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19302            ],
19303            cx,
19304        );
19305        multibuffer.push_excerpts(
19306            buffer_3.clone(),
19307            [
19308                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19309                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19310                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19311            ],
19312            cx,
19313        );
19314        multibuffer
19315    });
19316
19317    let fs = FakeFs::new(cx.executor());
19318    fs.insert_tree(
19319        "/a",
19320        json!({
19321            "main.rs": sample_text_1,
19322            "other.rs": sample_text_2,
19323            "lib.rs": sample_text_3,
19324        }),
19325    )
19326    .await;
19327    let project = Project::test(fs, ["/a".as_ref()], cx).await;
19328    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19329    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19330    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19331        Editor::new(
19332            EditorMode::full(),
19333            multi_buffer,
19334            Some(project.clone()),
19335            window,
19336            cx,
19337        )
19338    });
19339    let multibuffer_item_id = workspace
19340        .update(cx, |workspace, window, cx| {
19341            assert!(
19342                workspace.active_item(cx).is_none(),
19343                "active item should be None before the first item is added"
19344            );
19345            workspace.add_item_to_active_pane(
19346                Box::new(multi_buffer_editor.clone()),
19347                None,
19348                true,
19349                window,
19350                cx,
19351            );
19352            let active_item = workspace
19353                .active_item(cx)
19354                .expect("should have an active item after adding the multi buffer");
19355            assert_eq!(
19356                active_item.buffer_kind(cx),
19357                ItemBufferKind::Multibuffer,
19358                "A multi buffer was expected to active after adding"
19359            );
19360            active_item.item_id()
19361        })
19362        .unwrap();
19363    cx.executor().run_until_parked();
19364
19365    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19366        editor.change_selections(
19367            SelectionEffects::scroll(Autoscroll::Next),
19368            window,
19369            cx,
19370            |s| s.select_ranges(Some(1..2)),
19371        );
19372        editor.open_excerpts(&OpenExcerpts, window, cx);
19373    });
19374    cx.executor().run_until_parked();
19375    let first_item_id = workspace
19376        .update(cx, |workspace, window, cx| {
19377            let active_item = workspace
19378                .active_item(cx)
19379                .expect("should have an active item after navigating into the 1st buffer");
19380            let first_item_id = active_item.item_id();
19381            assert_ne!(
19382                first_item_id, multibuffer_item_id,
19383                "Should navigate into the 1st buffer and activate it"
19384            );
19385            assert_eq!(
19386                active_item.buffer_kind(cx),
19387                ItemBufferKind::Singleton,
19388                "New active item should be a singleton buffer"
19389            );
19390            assert_eq!(
19391                active_item
19392                    .act_as::<Editor>(cx)
19393                    .expect("should have navigated into an editor for the 1st buffer")
19394                    .read(cx)
19395                    .text(cx),
19396                sample_text_1
19397            );
19398
19399            workspace
19400                .go_back(workspace.active_pane().downgrade(), window, cx)
19401                .detach_and_log_err(cx);
19402
19403            first_item_id
19404        })
19405        .unwrap();
19406    cx.executor().run_until_parked();
19407    workspace
19408        .update(cx, |workspace, _, cx| {
19409            let active_item = workspace
19410                .active_item(cx)
19411                .expect("should have an active item after navigating back");
19412            assert_eq!(
19413                active_item.item_id(),
19414                multibuffer_item_id,
19415                "Should navigate back to the multi buffer"
19416            );
19417            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19418        })
19419        .unwrap();
19420
19421    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19422        editor.change_selections(
19423            SelectionEffects::scroll(Autoscroll::Next),
19424            window,
19425            cx,
19426            |s| s.select_ranges(Some(39..40)),
19427        );
19428        editor.open_excerpts(&OpenExcerpts, window, cx);
19429    });
19430    cx.executor().run_until_parked();
19431    let second_item_id = workspace
19432        .update(cx, |workspace, window, cx| {
19433            let active_item = workspace
19434                .active_item(cx)
19435                .expect("should have an active item after navigating into the 2nd buffer");
19436            let second_item_id = active_item.item_id();
19437            assert_ne!(
19438                second_item_id, multibuffer_item_id,
19439                "Should navigate away from the multibuffer"
19440            );
19441            assert_ne!(
19442                second_item_id, first_item_id,
19443                "Should navigate into the 2nd buffer and activate it"
19444            );
19445            assert_eq!(
19446                active_item.buffer_kind(cx),
19447                ItemBufferKind::Singleton,
19448                "New active item should be a singleton buffer"
19449            );
19450            assert_eq!(
19451                active_item
19452                    .act_as::<Editor>(cx)
19453                    .expect("should have navigated into an editor")
19454                    .read(cx)
19455                    .text(cx),
19456                sample_text_2
19457            );
19458
19459            workspace
19460                .go_back(workspace.active_pane().downgrade(), window, cx)
19461                .detach_and_log_err(cx);
19462
19463            second_item_id
19464        })
19465        .unwrap();
19466    cx.executor().run_until_parked();
19467    workspace
19468        .update(cx, |workspace, _, cx| {
19469            let active_item = workspace
19470                .active_item(cx)
19471                .expect("should have an active item after navigating back from the 2nd buffer");
19472            assert_eq!(
19473                active_item.item_id(),
19474                multibuffer_item_id,
19475                "Should navigate back from the 2nd buffer to the multi buffer"
19476            );
19477            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19478        })
19479        .unwrap();
19480
19481    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19482        editor.change_selections(
19483            SelectionEffects::scroll(Autoscroll::Next),
19484            window,
19485            cx,
19486            |s| s.select_ranges(Some(70..70)),
19487        );
19488        editor.open_excerpts(&OpenExcerpts, window, cx);
19489    });
19490    cx.executor().run_until_parked();
19491    workspace
19492        .update(cx, |workspace, window, cx| {
19493            let active_item = workspace
19494                .active_item(cx)
19495                .expect("should have an active item after navigating into the 3rd buffer");
19496            let third_item_id = active_item.item_id();
19497            assert_ne!(
19498                third_item_id, multibuffer_item_id,
19499                "Should navigate into the 3rd buffer and activate it"
19500            );
19501            assert_ne!(third_item_id, first_item_id);
19502            assert_ne!(third_item_id, second_item_id);
19503            assert_eq!(
19504                active_item.buffer_kind(cx),
19505                ItemBufferKind::Singleton,
19506                "New active item should be a singleton buffer"
19507            );
19508            assert_eq!(
19509                active_item
19510                    .act_as::<Editor>(cx)
19511                    .expect("should have navigated into an editor")
19512                    .read(cx)
19513                    .text(cx),
19514                sample_text_3
19515            );
19516
19517            workspace
19518                .go_back(workspace.active_pane().downgrade(), window, cx)
19519                .detach_and_log_err(cx);
19520        })
19521        .unwrap();
19522    cx.executor().run_until_parked();
19523    workspace
19524        .update(cx, |workspace, _, cx| {
19525            let active_item = workspace
19526                .active_item(cx)
19527                .expect("should have an active item after navigating back from the 3rd buffer");
19528            assert_eq!(
19529                active_item.item_id(),
19530                multibuffer_item_id,
19531                "Should navigate back from the 3rd buffer to the multi buffer"
19532            );
19533            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19534        })
19535        .unwrap();
19536}
19537
19538#[gpui::test]
19539async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19540    init_test(cx, |_| {});
19541
19542    let mut cx = EditorTestContext::new(cx).await;
19543
19544    let diff_base = r#"
19545        use some::mod;
19546
19547        const A: u32 = 42;
19548
19549        fn main() {
19550            println!("hello");
19551
19552            println!("world");
19553        }
19554        "#
19555    .unindent();
19556
19557    cx.set_state(
19558        &r#"
19559        use some::modified;
19560
19561        ˇ
19562        fn main() {
19563            println!("hello there");
19564
19565            println!("around the");
19566            println!("world");
19567        }
19568        "#
19569        .unindent(),
19570    );
19571
19572    cx.set_head_text(&diff_base);
19573    executor.run_until_parked();
19574
19575    cx.update_editor(|editor, window, cx| {
19576        editor.go_to_next_hunk(&GoToHunk, window, cx);
19577        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19578    });
19579    executor.run_until_parked();
19580    cx.assert_state_with_diff(
19581        r#"
19582          use some::modified;
19583
19584
19585          fn main() {
19586        -     println!("hello");
19587        + ˇ    println!("hello there");
19588
19589              println!("around the");
19590              println!("world");
19591          }
19592        "#
19593        .unindent(),
19594    );
19595
19596    cx.update_editor(|editor, window, cx| {
19597        for _ in 0..2 {
19598            editor.go_to_next_hunk(&GoToHunk, window, cx);
19599            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19600        }
19601    });
19602    executor.run_until_parked();
19603    cx.assert_state_with_diff(
19604        r#"
19605        - use some::mod;
19606        + ˇuse some::modified;
19607
19608
19609          fn main() {
19610        -     println!("hello");
19611        +     println!("hello there");
19612
19613        +     println!("around the");
19614              println!("world");
19615          }
19616        "#
19617        .unindent(),
19618    );
19619
19620    cx.update_editor(|editor, window, cx| {
19621        editor.go_to_next_hunk(&GoToHunk, window, cx);
19622        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19623    });
19624    executor.run_until_parked();
19625    cx.assert_state_with_diff(
19626        r#"
19627        - use some::mod;
19628        + use some::modified;
19629
19630        - const A: u32 = 42;
19631          ˇ
19632          fn main() {
19633        -     println!("hello");
19634        +     println!("hello there");
19635
19636        +     println!("around the");
19637              println!("world");
19638          }
19639        "#
19640        .unindent(),
19641    );
19642
19643    cx.update_editor(|editor, window, cx| {
19644        editor.cancel(&Cancel, window, cx);
19645    });
19646
19647    cx.assert_state_with_diff(
19648        r#"
19649          use some::modified;
19650
19651          ˇ
19652          fn main() {
19653              println!("hello there");
19654
19655              println!("around the");
19656              println!("world");
19657          }
19658        "#
19659        .unindent(),
19660    );
19661}
19662
19663#[gpui::test]
19664async fn test_diff_base_change_with_expanded_diff_hunks(
19665    executor: BackgroundExecutor,
19666    cx: &mut TestAppContext,
19667) {
19668    init_test(cx, |_| {});
19669
19670    let mut cx = EditorTestContext::new(cx).await;
19671
19672    let diff_base = r#"
19673        use some::mod1;
19674        use some::mod2;
19675
19676        const A: u32 = 42;
19677        const B: u32 = 42;
19678        const C: u32 = 42;
19679
19680        fn main() {
19681            println!("hello");
19682
19683            println!("world");
19684        }
19685        "#
19686    .unindent();
19687
19688    cx.set_state(
19689        &r#"
19690        use some::mod2;
19691
19692        const A: u32 = 42;
19693        const C: u32 = 42;
19694
19695        fn main(ˇ) {
19696            //println!("hello");
19697
19698            println!("world");
19699            //
19700            //
19701        }
19702        "#
19703        .unindent(),
19704    );
19705
19706    cx.set_head_text(&diff_base);
19707    executor.run_until_parked();
19708
19709    cx.update_editor(|editor, window, cx| {
19710        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19711    });
19712    executor.run_until_parked();
19713    cx.assert_state_with_diff(
19714        r#"
19715        - use some::mod1;
19716          use some::mod2;
19717
19718          const A: u32 = 42;
19719        - const B: u32 = 42;
19720          const C: u32 = 42;
19721
19722          fn main(ˇ) {
19723        -     println!("hello");
19724        +     //println!("hello");
19725
19726              println!("world");
19727        +     //
19728        +     //
19729          }
19730        "#
19731        .unindent(),
19732    );
19733
19734    cx.set_head_text("new diff base!");
19735    executor.run_until_parked();
19736    cx.assert_state_with_diff(
19737        r#"
19738        - new diff base!
19739        + use some::mod2;
19740        +
19741        + const A: u32 = 42;
19742        + const C: u32 = 42;
19743        +
19744        + fn main(ˇ) {
19745        +     //println!("hello");
19746        +
19747        +     println!("world");
19748        +     //
19749        +     //
19750        + }
19751        "#
19752        .unindent(),
19753    );
19754}
19755
19756#[gpui::test]
19757async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
19758    init_test(cx, |_| {});
19759
19760    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19761    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19762    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19763    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19764    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
19765    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
19766
19767    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
19768    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
19769    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
19770
19771    let multi_buffer = cx.new(|cx| {
19772        let mut multibuffer = MultiBuffer::new(ReadWrite);
19773        multibuffer.push_excerpts(
19774            buffer_1.clone(),
19775            [
19776                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19777                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19778                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19779            ],
19780            cx,
19781        );
19782        multibuffer.push_excerpts(
19783            buffer_2.clone(),
19784            [
19785                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19786                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19787                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19788            ],
19789            cx,
19790        );
19791        multibuffer.push_excerpts(
19792            buffer_3.clone(),
19793            [
19794                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19795                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19796                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19797            ],
19798            cx,
19799        );
19800        multibuffer
19801    });
19802
19803    let editor =
19804        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19805    editor
19806        .update(cx, |editor, _window, cx| {
19807            for (buffer, diff_base) in [
19808                (buffer_1.clone(), file_1_old),
19809                (buffer_2.clone(), file_2_old),
19810                (buffer_3.clone(), file_3_old),
19811            ] {
19812                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
19813                editor
19814                    .buffer
19815                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
19816            }
19817        })
19818        .unwrap();
19819
19820    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19821    cx.run_until_parked();
19822
19823    cx.assert_editor_state(
19824        &"
19825            ˇaaa
19826            ccc
19827            ddd
19828
19829            ggg
19830            hhh
19831
19832
19833            lll
19834            mmm
19835            NNN
19836
19837            qqq
19838            rrr
19839
19840            uuu
19841            111
19842            222
19843            333
19844
19845            666
19846            777
19847
19848            000
19849            !!!"
19850        .unindent(),
19851    );
19852
19853    cx.update_editor(|editor, window, cx| {
19854        editor.select_all(&SelectAll, window, cx);
19855        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19856    });
19857    cx.executor().run_until_parked();
19858
19859    cx.assert_state_with_diff(
19860        "
19861            «aaa
19862          - bbb
19863            ccc
19864            ddd
19865
19866            ggg
19867            hhh
19868
19869
19870            lll
19871            mmm
19872          - nnn
19873          + NNN
19874
19875            qqq
19876            rrr
19877
19878            uuu
19879            111
19880            222
19881            333
19882
19883          + 666
19884            777
19885
19886            000
19887            !!!ˇ»"
19888            .unindent(),
19889    );
19890}
19891
19892#[gpui::test]
19893async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
19894    init_test(cx, |_| {});
19895
19896    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
19897    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
19898
19899    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
19900    let multi_buffer = cx.new(|cx| {
19901        let mut multibuffer = MultiBuffer::new(ReadWrite);
19902        multibuffer.push_excerpts(
19903            buffer.clone(),
19904            [
19905                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
19906                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
19907                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
19908            ],
19909            cx,
19910        );
19911        multibuffer
19912    });
19913
19914    let editor =
19915        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19916    editor
19917        .update(cx, |editor, _window, cx| {
19918            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
19919            editor
19920                .buffer
19921                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
19922        })
19923        .unwrap();
19924
19925    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19926    cx.run_until_parked();
19927
19928    cx.update_editor(|editor, window, cx| {
19929        editor.expand_all_diff_hunks(&Default::default(), window, cx)
19930    });
19931    cx.executor().run_until_parked();
19932
19933    // When the start of a hunk coincides with the start of its excerpt,
19934    // the hunk is expanded. When the start of a hunk is earlier than
19935    // the start of its excerpt, the hunk is not expanded.
19936    cx.assert_state_with_diff(
19937        "
19938            ˇaaa
19939          - bbb
19940          + BBB
19941
19942          - ddd
19943          - eee
19944          + DDD
19945          + EEE
19946            fff
19947
19948            iii
19949        "
19950        .unindent(),
19951    );
19952}
19953
19954#[gpui::test]
19955async fn test_edits_around_expanded_insertion_hunks(
19956    executor: BackgroundExecutor,
19957    cx: &mut TestAppContext,
19958) {
19959    init_test(cx, |_| {});
19960
19961    let mut cx = EditorTestContext::new(cx).await;
19962
19963    let diff_base = r#"
19964        use some::mod1;
19965        use some::mod2;
19966
19967        const A: u32 = 42;
19968
19969        fn main() {
19970            println!("hello");
19971
19972            println!("world");
19973        }
19974        "#
19975    .unindent();
19976    executor.run_until_parked();
19977    cx.set_state(
19978        &r#"
19979        use some::mod1;
19980        use some::mod2;
19981
19982        const A: u32 = 42;
19983        const B: u32 = 42;
19984        const C: u32 = 42;
19985        ˇ
19986
19987        fn main() {
19988            println!("hello");
19989
19990            println!("world");
19991        }
19992        "#
19993        .unindent(),
19994    );
19995
19996    cx.set_head_text(&diff_base);
19997    executor.run_until_parked();
19998
19999    cx.update_editor(|editor, window, cx| {
20000        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20001    });
20002    executor.run_until_parked();
20003
20004    cx.assert_state_with_diff(
20005        r#"
20006        use some::mod1;
20007        use some::mod2;
20008
20009        const A: u32 = 42;
20010      + const B: u32 = 42;
20011      + const C: u32 = 42;
20012      + ˇ
20013
20014        fn main() {
20015            println!("hello");
20016
20017            println!("world");
20018        }
20019      "#
20020        .unindent(),
20021    );
20022
20023    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
20024    executor.run_until_parked();
20025
20026    cx.assert_state_with_diff(
20027        r#"
20028        use some::mod1;
20029        use some::mod2;
20030
20031        const A: u32 = 42;
20032      + const B: u32 = 42;
20033      + const C: u32 = 42;
20034      + const D: u32 = 42;
20035      + ˇ
20036
20037        fn main() {
20038            println!("hello");
20039
20040            println!("world");
20041        }
20042      "#
20043        .unindent(),
20044    );
20045
20046    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
20047    executor.run_until_parked();
20048
20049    cx.assert_state_with_diff(
20050        r#"
20051        use some::mod1;
20052        use some::mod2;
20053
20054        const A: u32 = 42;
20055      + const B: u32 = 42;
20056      + const C: u32 = 42;
20057      + const D: u32 = 42;
20058      + const E: u32 = 42;
20059      + ˇ
20060
20061        fn main() {
20062            println!("hello");
20063
20064            println!("world");
20065        }
20066      "#
20067        .unindent(),
20068    );
20069
20070    cx.update_editor(|editor, window, cx| {
20071        editor.delete_line(&DeleteLine, window, cx);
20072    });
20073    executor.run_until_parked();
20074
20075    cx.assert_state_with_diff(
20076        r#"
20077        use some::mod1;
20078        use some::mod2;
20079
20080        const A: u32 = 42;
20081      + const B: u32 = 42;
20082      + const C: u32 = 42;
20083      + const D: u32 = 42;
20084      + const E: u32 = 42;
20085        ˇ
20086        fn main() {
20087            println!("hello");
20088
20089            println!("world");
20090        }
20091      "#
20092        .unindent(),
20093    );
20094
20095    cx.update_editor(|editor, window, cx| {
20096        editor.move_up(&MoveUp, window, cx);
20097        editor.delete_line(&DeleteLine, window, cx);
20098        editor.move_up(&MoveUp, window, cx);
20099        editor.delete_line(&DeleteLine, window, cx);
20100        editor.move_up(&MoveUp, window, cx);
20101        editor.delete_line(&DeleteLine, window, cx);
20102    });
20103    executor.run_until_parked();
20104    cx.assert_state_with_diff(
20105        r#"
20106        use some::mod1;
20107        use some::mod2;
20108
20109        const A: u32 = 42;
20110      + const B: u32 = 42;
20111        ˇ
20112        fn main() {
20113            println!("hello");
20114
20115            println!("world");
20116        }
20117      "#
20118        .unindent(),
20119    );
20120
20121    cx.update_editor(|editor, window, cx| {
20122        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
20123        editor.delete_line(&DeleteLine, window, cx);
20124    });
20125    executor.run_until_parked();
20126    cx.assert_state_with_diff(
20127        r#"
20128        ˇ
20129        fn main() {
20130            println!("hello");
20131
20132            println!("world");
20133        }
20134      "#
20135        .unindent(),
20136    );
20137}
20138
20139#[gpui::test]
20140async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
20141    init_test(cx, |_| {});
20142
20143    let mut cx = EditorTestContext::new(cx).await;
20144    cx.set_head_text(indoc! { "
20145        one
20146        two
20147        three
20148        four
20149        five
20150        "
20151    });
20152    cx.set_state(indoc! { "
20153        one
20154        ˇthree
20155        five
20156    "});
20157    cx.run_until_parked();
20158    cx.update_editor(|editor, window, cx| {
20159        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20160    });
20161    cx.assert_state_with_diff(
20162        indoc! { "
20163        one
20164      - two
20165        ˇthree
20166      - four
20167        five
20168    "}
20169        .to_string(),
20170    );
20171    cx.update_editor(|editor, window, cx| {
20172        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20173    });
20174
20175    cx.assert_state_with_diff(
20176        indoc! { "
20177        one
20178        ˇthree
20179        five
20180    "}
20181        .to_string(),
20182    );
20183
20184    cx.set_state(indoc! { "
20185        one
20186        ˇTWO
20187        three
20188        four
20189        five
20190    "});
20191    cx.run_until_parked();
20192    cx.update_editor(|editor, window, cx| {
20193        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20194    });
20195
20196    cx.assert_state_with_diff(
20197        indoc! { "
20198            one
20199          - two
20200          + ˇTWO
20201            three
20202            four
20203            five
20204        "}
20205        .to_string(),
20206    );
20207    cx.update_editor(|editor, window, cx| {
20208        editor.move_up(&Default::default(), window, cx);
20209        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20210    });
20211    cx.assert_state_with_diff(
20212        indoc! { "
20213            one
20214            ˇTWO
20215            three
20216            four
20217            five
20218        "}
20219        .to_string(),
20220    );
20221}
20222
20223#[gpui::test]
20224async fn test_edits_around_expanded_deletion_hunks(
20225    executor: BackgroundExecutor,
20226    cx: &mut TestAppContext,
20227) {
20228    init_test(cx, |_| {});
20229
20230    let mut cx = EditorTestContext::new(cx).await;
20231
20232    let diff_base = r#"
20233        use some::mod1;
20234        use some::mod2;
20235
20236        const A: u32 = 42;
20237        const B: u32 = 42;
20238        const C: u32 = 42;
20239
20240
20241        fn main() {
20242            println!("hello");
20243
20244            println!("world");
20245        }
20246    "#
20247    .unindent();
20248    executor.run_until_parked();
20249    cx.set_state(
20250        &r#"
20251        use some::mod1;
20252        use some::mod2;
20253
20254        ˇconst B: u32 = 42;
20255        const C: u32 = 42;
20256
20257
20258        fn main() {
20259            println!("hello");
20260
20261            println!("world");
20262        }
20263        "#
20264        .unindent(),
20265    );
20266
20267    cx.set_head_text(&diff_base);
20268    executor.run_until_parked();
20269
20270    cx.update_editor(|editor, window, cx| {
20271        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20272    });
20273    executor.run_until_parked();
20274
20275    cx.assert_state_with_diff(
20276        r#"
20277        use some::mod1;
20278        use some::mod2;
20279
20280      - const A: u32 = 42;
20281        ˇconst B: u32 = 42;
20282        const C: u32 = 42;
20283
20284
20285        fn main() {
20286            println!("hello");
20287
20288            println!("world");
20289        }
20290      "#
20291        .unindent(),
20292    );
20293
20294    cx.update_editor(|editor, window, cx| {
20295        editor.delete_line(&DeleteLine, window, cx);
20296    });
20297    executor.run_until_parked();
20298    cx.assert_state_with_diff(
20299        r#"
20300        use some::mod1;
20301        use some::mod2;
20302
20303      - const A: u32 = 42;
20304      - const B: u32 = 42;
20305        ˇconst C: u32 = 42;
20306
20307
20308        fn main() {
20309            println!("hello");
20310
20311            println!("world");
20312        }
20313      "#
20314        .unindent(),
20315    );
20316
20317    cx.update_editor(|editor, window, cx| {
20318        editor.delete_line(&DeleteLine, window, cx);
20319    });
20320    executor.run_until_parked();
20321    cx.assert_state_with_diff(
20322        r#"
20323        use some::mod1;
20324        use some::mod2;
20325
20326      - const A: u32 = 42;
20327      - const B: u32 = 42;
20328      - const C: u32 = 42;
20329        ˇ
20330
20331        fn main() {
20332            println!("hello");
20333
20334            println!("world");
20335        }
20336      "#
20337        .unindent(),
20338    );
20339
20340    cx.update_editor(|editor, window, cx| {
20341        editor.handle_input("replacement", window, cx);
20342    });
20343    executor.run_until_parked();
20344    cx.assert_state_with_diff(
20345        r#"
20346        use some::mod1;
20347        use some::mod2;
20348
20349      - const A: u32 = 42;
20350      - const B: u32 = 42;
20351      - const C: u32 = 42;
20352      -
20353      + replacementˇ
20354
20355        fn main() {
20356            println!("hello");
20357
20358            println!("world");
20359        }
20360      "#
20361        .unindent(),
20362    );
20363}
20364
20365#[gpui::test]
20366async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20367    init_test(cx, |_| {});
20368
20369    let mut cx = EditorTestContext::new(cx).await;
20370
20371    let base_text = r#"
20372        one
20373        two
20374        three
20375        four
20376        five
20377    "#
20378    .unindent();
20379    executor.run_until_parked();
20380    cx.set_state(
20381        &r#"
20382        one
20383        two
20384        fˇour
20385        five
20386        "#
20387        .unindent(),
20388    );
20389
20390    cx.set_head_text(&base_text);
20391    executor.run_until_parked();
20392
20393    cx.update_editor(|editor, window, cx| {
20394        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20395    });
20396    executor.run_until_parked();
20397
20398    cx.assert_state_with_diff(
20399        r#"
20400          one
20401          two
20402        - three
20403          fˇour
20404          five
20405        "#
20406        .unindent(),
20407    );
20408
20409    cx.update_editor(|editor, window, cx| {
20410        editor.backspace(&Backspace, window, cx);
20411        editor.backspace(&Backspace, window, cx);
20412    });
20413    executor.run_until_parked();
20414    cx.assert_state_with_diff(
20415        r#"
20416          one
20417          two
20418        - threeˇ
20419        - four
20420        + our
20421          five
20422        "#
20423        .unindent(),
20424    );
20425}
20426
20427#[gpui::test]
20428async fn test_edit_after_expanded_modification_hunk(
20429    executor: BackgroundExecutor,
20430    cx: &mut TestAppContext,
20431) {
20432    init_test(cx, |_| {});
20433
20434    let mut cx = EditorTestContext::new(cx).await;
20435
20436    let diff_base = r#"
20437        use some::mod1;
20438        use some::mod2;
20439
20440        const A: u32 = 42;
20441        const B: u32 = 42;
20442        const C: u32 = 42;
20443        const D: u32 = 42;
20444
20445
20446        fn main() {
20447            println!("hello");
20448
20449            println!("world");
20450        }"#
20451    .unindent();
20452
20453    cx.set_state(
20454        &r#"
20455        use some::mod1;
20456        use some::mod2;
20457
20458        const A: u32 = 42;
20459        const B: u32 = 42;
20460        const C: u32 = 43ˇ
20461        const D: u32 = 42;
20462
20463
20464        fn main() {
20465            println!("hello");
20466
20467            println!("world");
20468        }"#
20469        .unindent(),
20470    );
20471
20472    cx.set_head_text(&diff_base);
20473    executor.run_until_parked();
20474    cx.update_editor(|editor, window, cx| {
20475        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20476    });
20477    executor.run_until_parked();
20478
20479    cx.assert_state_with_diff(
20480        r#"
20481        use some::mod1;
20482        use some::mod2;
20483
20484        const A: u32 = 42;
20485        const B: u32 = 42;
20486      - const C: u32 = 42;
20487      + const C: u32 = 43ˇ
20488        const D: u32 = 42;
20489
20490
20491        fn main() {
20492            println!("hello");
20493
20494            println!("world");
20495        }"#
20496        .unindent(),
20497    );
20498
20499    cx.update_editor(|editor, window, cx| {
20500        editor.handle_input("\nnew_line\n", window, cx);
20501    });
20502    executor.run_until_parked();
20503
20504    cx.assert_state_with_diff(
20505        r#"
20506        use some::mod1;
20507        use some::mod2;
20508
20509        const A: u32 = 42;
20510        const B: u32 = 42;
20511      - const C: u32 = 42;
20512      + const C: u32 = 43
20513      + new_line
20514      + ˇ
20515        const D: u32 = 42;
20516
20517
20518        fn main() {
20519            println!("hello");
20520
20521            println!("world");
20522        }"#
20523        .unindent(),
20524    );
20525}
20526
20527#[gpui::test]
20528async fn test_stage_and_unstage_added_file_hunk(
20529    executor: BackgroundExecutor,
20530    cx: &mut TestAppContext,
20531) {
20532    init_test(cx, |_| {});
20533
20534    let mut cx = EditorTestContext::new(cx).await;
20535    cx.update_editor(|editor, _, cx| {
20536        editor.set_expand_all_diff_hunks(cx);
20537    });
20538
20539    let working_copy = r#"
20540            ˇfn main() {
20541                println!("hello, world!");
20542            }
20543        "#
20544    .unindent();
20545
20546    cx.set_state(&working_copy);
20547    executor.run_until_parked();
20548
20549    cx.assert_state_with_diff(
20550        r#"
20551            + ˇfn main() {
20552            +     println!("hello, world!");
20553            + }
20554        "#
20555        .unindent(),
20556    );
20557    cx.assert_index_text(None);
20558
20559    cx.update_editor(|editor, window, cx| {
20560        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20561    });
20562    executor.run_until_parked();
20563    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
20564    cx.assert_state_with_diff(
20565        r#"
20566            + ˇfn main() {
20567            +     println!("hello, world!");
20568            + }
20569        "#
20570        .unindent(),
20571    );
20572
20573    cx.update_editor(|editor, window, cx| {
20574        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20575    });
20576    executor.run_until_parked();
20577    cx.assert_index_text(None);
20578}
20579
20580async fn setup_indent_guides_editor(
20581    text: &str,
20582    cx: &mut TestAppContext,
20583) -> (BufferId, EditorTestContext) {
20584    init_test(cx, |_| {});
20585
20586    let mut cx = EditorTestContext::new(cx).await;
20587
20588    let buffer_id = cx.update_editor(|editor, window, cx| {
20589        editor.set_text(text, window, cx);
20590        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
20591
20592        buffer_ids[0]
20593    });
20594
20595    (buffer_id, cx)
20596}
20597
20598fn assert_indent_guides(
20599    range: Range<u32>,
20600    expected: Vec<IndentGuide>,
20601    active_indices: Option<Vec<usize>>,
20602    cx: &mut EditorTestContext,
20603) {
20604    let indent_guides = cx.update_editor(|editor, window, cx| {
20605        let snapshot = editor.snapshot(window, cx).display_snapshot;
20606        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
20607            editor,
20608            MultiBufferRow(range.start)..MultiBufferRow(range.end),
20609            true,
20610            &snapshot,
20611            cx,
20612        );
20613
20614        indent_guides.sort_by(|a, b| {
20615            a.depth.cmp(&b.depth).then(
20616                a.start_row
20617                    .cmp(&b.start_row)
20618                    .then(a.end_row.cmp(&b.end_row)),
20619            )
20620        });
20621        indent_guides
20622    });
20623
20624    if let Some(expected) = active_indices {
20625        let active_indices = cx.update_editor(|editor, window, cx| {
20626            let snapshot = editor.snapshot(window, cx).display_snapshot;
20627            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
20628        });
20629
20630        assert_eq!(
20631            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
20632            expected,
20633            "Active indent guide indices do not match"
20634        );
20635    }
20636
20637    assert_eq!(indent_guides, expected, "Indent guides do not match");
20638}
20639
20640fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
20641    IndentGuide {
20642        buffer_id,
20643        start_row: MultiBufferRow(start_row),
20644        end_row: MultiBufferRow(end_row),
20645        depth,
20646        tab_size: 4,
20647        settings: IndentGuideSettings {
20648            enabled: true,
20649            line_width: 1,
20650            active_line_width: 1,
20651            coloring: IndentGuideColoring::default(),
20652            background_coloring: IndentGuideBackgroundColoring::default(),
20653        },
20654    }
20655}
20656
20657#[gpui::test]
20658async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
20659    let (buffer_id, mut cx) = setup_indent_guides_editor(
20660        &"
20661        fn main() {
20662            let a = 1;
20663        }"
20664        .unindent(),
20665        cx,
20666    )
20667    .await;
20668
20669    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20670}
20671
20672#[gpui::test]
20673async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
20674    let (buffer_id, mut cx) = setup_indent_guides_editor(
20675        &"
20676        fn main() {
20677            let a = 1;
20678            let b = 2;
20679        }"
20680        .unindent(),
20681        cx,
20682    )
20683    .await;
20684
20685    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
20686}
20687
20688#[gpui::test]
20689async fn test_indent_guide_nested(cx: &mut TestAppContext) {
20690    let (buffer_id, mut cx) = setup_indent_guides_editor(
20691        &"
20692        fn main() {
20693            let a = 1;
20694            if a == 3 {
20695                let b = 2;
20696            } else {
20697                let c = 3;
20698            }
20699        }"
20700        .unindent(),
20701        cx,
20702    )
20703    .await;
20704
20705    assert_indent_guides(
20706        0..8,
20707        vec![
20708            indent_guide(buffer_id, 1, 6, 0),
20709            indent_guide(buffer_id, 3, 3, 1),
20710            indent_guide(buffer_id, 5, 5, 1),
20711        ],
20712        None,
20713        &mut cx,
20714    );
20715}
20716
20717#[gpui::test]
20718async fn test_indent_guide_tab(cx: &mut TestAppContext) {
20719    let (buffer_id, mut cx) = setup_indent_guides_editor(
20720        &"
20721        fn main() {
20722            let a = 1;
20723                let b = 2;
20724            let c = 3;
20725        }"
20726        .unindent(),
20727        cx,
20728    )
20729    .await;
20730
20731    assert_indent_guides(
20732        0..5,
20733        vec![
20734            indent_guide(buffer_id, 1, 3, 0),
20735            indent_guide(buffer_id, 2, 2, 1),
20736        ],
20737        None,
20738        &mut cx,
20739    );
20740}
20741
20742#[gpui::test]
20743async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
20744    let (buffer_id, mut cx) = setup_indent_guides_editor(
20745        &"
20746        fn main() {
20747            let a = 1;
20748
20749            let c = 3;
20750        }"
20751        .unindent(),
20752        cx,
20753    )
20754    .await;
20755
20756    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
20757}
20758
20759#[gpui::test]
20760async fn test_indent_guide_complex(cx: &mut TestAppContext) {
20761    let (buffer_id, mut cx) = setup_indent_guides_editor(
20762        &"
20763        fn main() {
20764            let a = 1;
20765
20766            let c = 3;
20767
20768            if a == 3 {
20769                let b = 2;
20770            } else {
20771                let c = 3;
20772            }
20773        }"
20774        .unindent(),
20775        cx,
20776    )
20777    .await;
20778
20779    assert_indent_guides(
20780        0..11,
20781        vec![
20782            indent_guide(buffer_id, 1, 9, 0),
20783            indent_guide(buffer_id, 6, 6, 1),
20784            indent_guide(buffer_id, 8, 8, 1),
20785        ],
20786        None,
20787        &mut cx,
20788    );
20789}
20790
20791#[gpui::test]
20792async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
20793    let (buffer_id, mut cx) = setup_indent_guides_editor(
20794        &"
20795        fn main() {
20796            let a = 1;
20797
20798            let c = 3;
20799
20800            if a == 3 {
20801                let b = 2;
20802            } else {
20803                let c = 3;
20804            }
20805        }"
20806        .unindent(),
20807        cx,
20808    )
20809    .await;
20810
20811    assert_indent_guides(
20812        1..11,
20813        vec![
20814            indent_guide(buffer_id, 1, 9, 0),
20815            indent_guide(buffer_id, 6, 6, 1),
20816            indent_guide(buffer_id, 8, 8, 1),
20817        ],
20818        None,
20819        &mut cx,
20820    );
20821}
20822
20823#[gpui::test]
20824async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
20825    let (buffer_id, mut cx) = setup_indent_guides_editor(
20826        &"
20827        fn main() {
20828            let a = 1;
20829
20830            let c = 3;
20831
20832            if a == 3 {
20833                let b = 2;
20834            } else {
20835                let c = 3;
20836            }
20837        }"
20838        .unindent(),
20839        cx,
20840    )
20841    .await;
20842
20843    assert_indent_guides(
20844        1..10,
20845        vec![
20846            indent_guide(buffer_id, 1, 9, 0),
20847            indent_guide(buffer_id, 6, 6, 1),
20848            indent_guide(buffer_id, 8, 8, 1),
20849        ],
20850        None,
20851        &mut cx,
20852    );
20853}
20854
20855#[gpui::test]
20856async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
20857    let (buffer_id, mut cx) = setup_indent_guides_editor(
20858        &"
20859        fn main() {
20860            if a {
20861                b(
20862                    c,
20863                    d,
20864                )
20865            } else {
20866                e(
20867                    f
20868                )
20869            }
20870        }"
20871        .unindent(),
20872        cx,
20873    )
20874    .await;
20875
20876    assert_indent_guides(
20877        0..11,
20878        vec![
20879            indent_guide(buffer_id, 1, 10, 0),
20880            indent_guide(buffer_id, 2, 5, 1),
20881            indent_guide(buffer_id, 7, 9, 1),
20882            indent_guide(buffer_id, 3, 4, 2),
20883            indent_guide(buffer_id, 8, 8, 2),
20884        ],
20885        None,
20886        &mut cx,
20887    );
20888
20889    cx.update_editor(|editor, window, cx| {
20890        editor.fold_at(MultiBufferRow(2), window, cx);
20891        assert_eq!(
20892            editor.display_text(cx),
20893            "
20894            fn main() {
20895                if a {
20896                    b(⋯
20897                    )
20898                } else {
20899                    e(
20900                        f
20901                    )
20902                }
20903            }"
20904            .unindent()
20905        );
20906    });
20907
20908    assert_indent_guides(
20909        0..11,
20910        vec![
20911            indent_guide(buffer_id, 1, 10, 0),
20912            indent_guide(buffer_id, 2, 5, 1),
20913            indent_guide(buffer_id, 7, 9, 1),
20914            indent_guide(buffer_id, 8, 8, 2),
20915        ],
20916        None,
20917        &mut cx,
20918    );
20919}
20920
20921#[gpui::test]
20922async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
20923    let (buffer_id, mut cx) = setup_indent_guides_editor(
20924        &"
20925        block1
20926            block2
20927                block3
20928                    block4
20929            block2
20930        block1
20931        block1"
20932            .unindent(),
20933        cx,
20934    )
20935    .await;
20936
20937    assert_indent_guides(
20938        1..10,
20939        vec![
20940            indent_guide(buffer_id, 1, 4, 0),
20941            indent_guide(buffer_id, 2, 3, 1),
20942            indent_guide(buffer_id, 3, 3, 2),
20943        ],
20944        None,
20945        &mut cx,
20946    );
20947}
20948
20949#[gpui::test]
20950async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
20951    let (buffer_id, mut cx) = setup_indent_guides_editor(
20952        &"
20953        block1
20954            block2
20955                block3
20956
20957        block1
20958        block1"
20959            .unindent(),
20960        cx,
20961    )
20962    .await;
20963
20964    assert_indent_guides(
20965        0..6,
20966        vec![
20967            indent_guide(buffer_id, 1, 2, 0),
20968            indent_guide(buffer_id, 2, 2, 1),
20969        ],
20970        None,
20971        &mut cx,
20972    );
20973}
20974
20975#[gpui::test]
20976async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
20977    let (buffer_id, mut cx) = setup_indent_guides_editor(
20978        &"
20979        function component() {
20980        \treturn (
20981        \t\t\t
20982        \t\t<div>
20983        \t\t\t<abc></abc>
20984        \t\t</div>
20985        \t)
20986        }"
20987        .unindent(),
20988        cx,
20989    )
20990    .await;
20991
20992    assert_indent_guides(
20993        0..8,
20994        vec![
20995            indent_guide(buffer_id, 1, 6, 0),
20996            indent_guide(buffer_id, 2, 5, 1),
20997            indent_guide(buffer_id, 4, 4, 2),
20998        ],
20999        None,
21000        &mut cx,
21001    );
21002}
21003
21004#[gpui::test]
21005async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
21006    let (buffer_id, mut cx) = setup_indent_guides_editor(
21007        &"
21008        function component() {
21009        \treturn (
21010        \t
21011        \t\t<div>
21012        \t\t\t<abc></abc>
21013        \t\t</div>
21014        \t)
21015        }"
21016        .unindent(),
21017        cx,
21018    )
21019    .await;
21020
21021    assert_indent_guides(
21022        0..8,
21023        vec![
21024            indent_guide(buffer_id, 1, 6, 0),
21025            indent_guide(buffer_id, 2, 5, 1),
21026            indent_guide(buffer_id, 4, 4, 2),
21027        ],
21028        None,
21029        &mut cx,
21030    );
21031}
21032
21033#[gpui::test]
21034async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
21035    let (buffer_id, mut cx) = setup_indent_guides_editor(
21036        &"
21037        block1
21038
21039
21040
21041            block2
21042        "
21043        .unindent(),
21044        cx,
21045    )
21046    .await;
21047
21048    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
21049}
21050
21051#[gpui::test]
21052async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
21053    let (buffer_id, mut cx) = setup_indent_guides_editor(
21054        &"
21055        def a:
21056        \tb = 3
21057        \tif True:
21058        \t\tc = 4
21059        \t\td = 5
21060        \tprint(b)
21061        "
21062        .unindent(),
21063        cx,
21064    )
21065    .await;
21066
21067    assert_indent_guides(
21068        0..6,
21069        vec![
21070            indent_guide(buffer_id, 1, 5, 0),
21071            indent_guide(buffer_id, 3, 4, 1),
21072        ],
21073        None,
21074        &mut cx,
21075    );
21076}
21077
21078#[gpui::test]
21079async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
21080    let (buffer_id, mut cx) = setup_indent_guides_editor(
21081        &"
21082    fn main() {
21083        let a = 1;
21084    }"
21085        .unindent(),
21086        cx,
21087    )
21088    .await;
21089
21090    cx.update_editor(|editor, window, cx| {
21091        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21092            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
21093        });
21094    });
21095
21096    assert_indent_guides(
21097        0..3,
21098        vec![indent_guide(buffer_id, 1, 1, 0)],
21099        Some(vec![0]),
21100        &mut cx,
21101    );
21102}
21103
21104#[gpui::test]
21105async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
21106    let (buffer_id, mut cx) = setup_indent_guides_editor(
21107        &"
21108    fn main() {
21109        if 1 == 2 {
21110            let a = 1;
21111        }
21112    }"
21113        .unindent(),
21114        cx,
21115    )
21116    .await;
21117
21118    cx.update_editor(|editor, window, cx| {
21119        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21120            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
21121        });
21122    });
21123
21124    assert_indent_guides(
21125        0..4,
21126        vec![
21127            indent_guide(buffer_id, 1, 3, 0),
21128            indent_guide(buffer_id, 2, 2, 1),
21129        ],
21130        Some(vec![1]),
21131        &mut cx,
21132    );
21133
21134    cx.update_editor(|editor, window, cx| {
21135        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21136            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
21137        });
21138    });
21139
21140    assert_indent_guides(
21141        0..4,
21142        vec![
21143            indent_guide(buffer_id, 1, 3, 0),
21144            indent_guide(buffer_id, 2, 2, 1),
21145        ],
21146        Some(vec![1]),
21147        &mut cx,
21148    );
21149
21150    cx.update_editor(|editor, window, cx| {
21151        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21152            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
21153        });
21154    });
21155
21156    assert_indent_guides(
21157        0..4,
21158        vec![
21159            indent_guide(buffer_id, 1, 3, 0),
21160            indent_guide(buffer_id, 2, 2, 1),
21161        ],
21162        Some(vec![0]),
21163        &mut cx,
21164    );
21165}
21166
21167#[gpui::test]
21168async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
21169    let (buffer_id, mut cx) = setup_indent_guides_editor(
21170        &"
21171    fn main() {
21172        let a = 1;
21173
21174        let b = 2;
21175    }"
21176        .unindent(),
21177        cx,
21178    )
21179    .await;
21180
21181    cx.update_editor(|editor, window, cx| {
21182        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21183            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
21184        });
21185    });
21186
21187    assert_indent_guides(
21188        0..5,
21189        vec![indent_guide(buffer_id, 1, 3, 0)],
21190        Some(vec![0]),
21191        &mut cx,
21192    );
21193}
21194
21195#[gpui::test]
21196async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
21197    let (buffer_id, mut cx) = setup_indent_guides_editor(
21198        &"
21199    def m:
21200        a = 1
21201        pass"
21202            .unindent(),
21203        cx,
21204    )
21205    .await;
21206
21207    cx.update_editor(|editor, window, cx| {
21208        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21209            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
21210        });
21211    });
21212
21213    assert_indent_guides(
21214        0..3,
21215        vec![indent_guide(buffer_id, 1, 2, 0)],
21216        Some(vec![0]),
21217        &mut cx,
21218    );
21219}
21220
21221#[gpui::test]
21222async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
21223    init_test(cx, |_| {});
21224    let mut cx = EditorTestContext::new(cx).await;
21225    let text = indoc! {
21226        "
21227        impl A {
21228            fn b() {
21229                0;
21230                3;
21231                5;
21232                6;
21233                7;
21234            }
21235        }
21236        "
21237    };
21238    let base_text = indoc! {
21239        "
21240        impl A {
21241            fn b() {
21242                0;
21243                1;
21244                2;
21245                3;
21246                4;
21247            }
21248            fn c() {
21249                5;
21250                6;
21251                7;
21252            }
21253        }
21254        "
21255    };
21256
21257    cx.update_editor(|editor, window, cx| {
21258        editor.set_text(text, window, cx);
21259
21260        editor.buffer().update(cx, |multibuffer, cx| {
21261            let buffer = multibuffer.as_singleton().unwrap();
21262            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
21263
21264            multibuffer.set_all_diff_hunks_expanded(cx);
21265            multibuffer.add_diff(diff, cx);
21266
21267            buffer.read(cx).remote_id()
21268        })
21269    });
21270    cx.run_until_parked();
21271
21272    cx.assert_state_with_diff(
21273        indoc! { "
21274          impl A {
21275              fn b() {
21276                  0;
21277        -         1;
21278        -         2;
21279                  3;
21280        -         4;
21281        -     }
21282        -     fn c() {
21283                  5;
21284                  6;
21285                  7;
21286              }
21287          }
21288          ˇ"
21289        }
21290        .to_string(),
21291    );
21292
21293    let mut actual_guides = cx.update_editor(|editor, window, cx| {
21294        editor
21295            .snapshot(window, cx)
21296            .buffer_snapshot()
21297            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
21298            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
21299            .collect::<Vec<_>>()
21300    });
21301    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
21302    assert_eq!(
21303        actual_guides,
21304        vec![
21305            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
21306            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
21307            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
21308        ]
21309    );
21310}
21311
21312#[gpui::test]
21313async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
21314    init_test(cx, |_| {});
21315    let mut cx = EditorTestContext::new(cx).await;
21316
21317    let diff_base = r#"
21318        a
21319        b
21320        c
21321        "#
21322    .unindent();
21323
21324    cx.set_state(
21325        &r#"
21326        ˇA
21327        b
21328        C
21329        "#
21330        .unindent(),
21331    );
21332    cx.set_head_text(&diff_base);
21333    cx.update_editor(|editor, window, cx| {
21334        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21335    });
21336    executor.run_until_parked();
21337
21338    let both_hunks_expanded = r#"
21339        - a
21340        + ˇA
21341          b
21342        - c
21343        + C
21344        "#
21345    .unindent();
21346
21347    cx.assert_state_with_diff(both_hunks_expanded.clone());
21348
21349    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21350        let snapshot = editor.snapshot(window, cx);
21351        let hunks = editor
21352            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21353            .collect::<Vec<_>>();
21354        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21355        let buffer_id = hunks[0].buffer_id;
21356        hunks
21357            .into_iter()
21358            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
21359            .collect::<Vec<_>>()
21360    });
21361    assert_eq!(hunk_ranges.len(), 2);
21362
21363    cx.update_editor(|editor, _, cx| {
21364        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21365    });
21366    executor.run_until_parked();
21367
21368    let second_hunk_expanded = r#"
21369          ˇA
21370          b
21371        - c
21372        + C
21373        "#
21374    .unindent();
21375
21376    cx.assert_state_with_diff(second_hunk_expanded);
21377
21378    cx.update_editor(|editor, _, cx| {
21379        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21380    });
21381    executor.run_until_parked();
21382
21383    cx.assert_state_with_diff(both_hunks_expanded.clone());
21384
21385    cx.update_editor(|editor, _, cx| {
21386        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21387    });
21388    executor.run_until_parked();
21389
21390    let first_hunk_expanded = r#"
21391        - a
21392        + ˇA
21393          b
21394          C
21395        "#
21396    .unindent();
21397
21398    cx.assert_state_with_diff(first_hunk_expanded);
21399
21400    cx.update_editor(|editor, _, cx| {
21401        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21402    });
21403    executor.run_until_parked();
21404
21405    cx.assert_state_with_diff(both_hunks_expanded);
21406
21407    cx.set_state(
21408        &r#"
21409        ˇA
21410        b
21411        "#
21412        .unindent(),
21413    );
21414    cx.run_until_parked();
21415
21416    // TODO this cursor position seems bad
21417    cx.assert_state_with_diff(
21418        r#"
21419        - ˇa
21420        + A
21421          b
21422        "#
21423        .unindent(),
21424    );
21425
21426    cx.update_editor(|editor, window, cx| {
21427        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21428    });
21429
21430    cx.assert_state_with_diff(
21431        r#"
21432            - ˇa
21433            + A
21434              b
21435            - c
21436            "#
21437        .unindent(),
21438    );
21439
21440    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21441        let snapshot = editor.snapshot(window, cx);
21442        let hunks = editor
21443            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21444            .collect::<Vec<_>>();
21445        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21446        let buffer_id = hunks[0].buffer_id;
21447        hunks
21448            .into_iter()
21449            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
21450            .collect::<Vec<_>>()
21451    });
21452    assert_eq!(hunk_ranges.len(), 2);
21453
21454    cx.update_editor(|editor, _, cx| {
21455        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21456    });
21457    executor.run_until_parked();
21458
21459    cx.assert_state_with_diff(
21460        r#"
21461        - ˇa
21462        + A
21463          b
21464        "#
21465        .unindent(),
21466    );
21467}
21468
21469#[gpui::test]
21470async fn test_toggle_deletion_hunk_at_start_of_file(
21471    executor: BackgroundExecutor,
21472    cx: &mut TestAppContext,
21473) {
21474    init_test(cx, |_| {});
21475    let mut cx = EditorTestContext::new(cx).await;
21476
21477    let diff_base = r#"
21478        a
21479        b
21480        c
21481        "#
21482    .unindent();
21483
21484    cx.set_state(
21485        &r#"
21486        ˇb
21487        c
21488        "#
21489        .unindent(),
21490    );
21491    cx.set_head_text(&diff_base);
21492    cx.update_editor(|editor, window, cx| {
21493        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21494    });
21495    executor.run_until_parked();
21496
21497    let hunk_expanded = r#"
21498        - a
21499          ˇb
21500          c
21501        "#
21502    .unindent();
21503
21504    cx.assert_state_with_diff(hunk_expanded.clone());
21505
21506    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21507        let snapshot = editor.snapshot(window, cx);
21508        let hunks = editor
21509            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21510            .collect::<Vec<_>>();
21511        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21512        let buffer_id = hunks[0].buffer_id;
21513        hunks
21514            .into_iter()
21515            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
21516            .collect::<Vec<_>>()
21517    });
21518    assert_eq!(hunk_ranges.len(), 1);
21519
21520    cx.update_editor(|editor, _, cx| {
21521        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21522    });
21523    executor.run_until_parked();
21524
21525    let hunk_collapsed = r#"
21526          ˇb
21527          c
21528        "#
21529    .unindent();
21530
21531    cx.assert_state_with_diff(hunk_collapsed);
21532
21533    cx.update_editor(|editor, _, cx| {
21534        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21535    });
21536    executor.run_until_parked();
21537
21538    cx.assert_state_with_diff(hunk_expanded);
21539}
21540
21541#[gpui::test]
21542async fn test_display_diff_hunks(cx: &mut TestAppContext) {
21543    init_test(cx, |_| {});
21544
21545    let fs = FakeFs::new(cx.executor());
21546    fs.insert_tree(
21547        path!("/test"),
21548        json!({
21549            ".git": {},
21550            "file-1": "ONE\n",
21551            "file-2": "TWO\n",
21552            "file-3": "THREE\n",
21553        }),
21554    )
21555    .await;
21556
21557    fs.set_head_for_repo(
21558        path!("/test/.git").as_ref(),
21559        &[
21560            ("file-1", "one\n".into()),
21561            ("file-2", "two\n".into()),
21562            ("file-3", "three\n".into()),
21563        ],
21564        "deadbeef",
21565    );
21566
21567    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
21568    let mut buffers = vec![];
21569    for i in 1..=3 {
21570        let buffer = project
21571            .update(cx, |project, cx| {
21572                let path = format!(path!("/test/file-{}"), i);
21573                project.open_local_buffer(path, cx)
21574            })
21575            .await
21576            .unwrap();
21577        buffers.push(buffer);
21578    }
21579
21580    let multibuffer = cx.new(|cx| {
21581        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
21582        multibuffer.set_all_diff_hunks_expanded(cx);
21583        for buffer in &buffers {
21584            let snapshot = buffer.read(cx).snapshot();
21585            multibuffer.set_excerpts_for_path(
21586                PathKey::with_sort_prefix(0, buffer.read(cx).file().unwrap().path().clone()),
21587                buffer.clone(),
21588                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
21589                2,
21590                cx,
21591            );
21592        }
21593        multibuffer
21594    });
21595
21596    let editor = cx.add_window(|window, cx| {
21597        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
21598    });
21599    cx.run_until_parked();
21600
21601    let snapshot = editor
21602        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21603        .unwrap();
21604    let hunks = snapshot
21605        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
21606        .map(|hunk| match hunk {
21607            DisplayDiffHunk::Unfolded {
21608                display_row_range, ..
21609            } => display_row_range,
21610            DisplayDiffHunk::Folded { .. } => unreachable!(),
21611        })
21612        .collect::<Vec<_>>();
21613    assert_eq!(
21614        hunks,
21615        [
21616            DisplayRow(2)..DisplayRow(4),
21617            DisplayRow(7)..DisplayRow(9),
21618            DisplayRow(12)..DisplayRow(14),
21619        ]
21620    );
21621}
21622
21623#[gpui::test]
21624async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
21625    init_test(cx, |_| {});
21626
21627    let mut cx = EditorTestContext::new(cx).await;
21628    cx.set_head_text(indoc! { "
21629        one
21630        two
21631        three
21632        four
21633        five
21634        "
21635    });
21636    cx.set_index_text(indoc! { "
21637        one
21638        two
21639        three
21640        four
21641        five
21642        "
21643    });
21644    cx.set_state(indoc! {"
21645        one
21646        TWO
21647        ˇTHREE
21648        FOUR
21649        five
21650    "});
21651    cx.run_until_parked();
21652    cx.update_editor(|editor, window, cx| {
21653        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21654    });
21655    cx.run_until_parked();
21656    cx.assert_index_text(Some(indoc! {"
21657        one
21658        TWO
21659        THREE
21660        FOUR
21661        five
21662    "}));
21663    cx.set_state(indoc! { "
21664        one
21665        TWO
21666        ˇTHREE-HUNDRED
21667        FOUR
21668        five
21669    "});
21670    cx.run_until_parked();
21671    cx.update_editor(|editor, window, cx| {
21672        let snapshot = editor.snapshot(window, cx);
21673        let hunks = editor
21674            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21675            .collect::<Vec<_>>();
21676        assert_eq!(hunks.len(), 1);
21677        assert_eq!(
21678            hunks[0].status(),
21679            DiffHunkStatus {
21680                kind: DiffHunkStatusKind::Modified,
21681                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
21682            }
21683        );
21684
21685        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21686    });
21687    cx.run_until_parked();
21688    cx.assert_index_text(Some(indoc! {"
21689        one
21690        TWO
21691        THREE-HUNDRED
21692        FOUR
21693        five
21694    "}));
21695}
21696
21697#[gpui::test]
21698fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
21699    init_test(cx, |_| {});
21700
21701    let editor = cx.add_window(|window, cx| {
21702        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
21703        build_editor(buffer, window, cx)
21704    });
21705
21706    let render_args = Arc::new(Mutex::new(None));
21707    let snapshot = editor
21708        .update(cx, |editor, window, cx| {
21709            let snapshot = editor.buffer().read(cx).snapshot(cx);
21710            let range =
21711                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
21712
21713            struct RenderArgs {
21714                row: MultiBufferRow,
21715                folded: bool,
21716                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
21717            }
21718
21719            let crease = Crease::inline(
21720                range,
21721                FoldPlaceholder::test(),
21722                {
21723                    let toggle_callback = render_args.clone();
21724                    move |row, folded, callback, _window, _cx| {
21725                        *toggle_callback.lock() = Some(RenderArgs {
21726                            row,
21727                            folded,
21728                            callback,
21729                        });
21730                        div()
21731                    }
21732                },
21733                |_row, _folded, _window, _cx| div(),
21734            );
21735
21736            editor.insert_creases(Some(crease), cx);
21737            let snapshot = editor.snapshot(window, cx);
21738            let _div =
21739                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
21740            snapshot
21741        })
21742        .unwrap();
21743
21744    let render_args = render_args.lock().take().unwrap();
21745    assert_eq!(render_args.row, MultiBufferRow(1));
21746    assert!(!render_args.folded);
21747    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21748
21749    cx.update_window(*editor, |_, window, cx| {
21750        (render_args.callback)(true, window, cx)
21751    })
21752    .unwrap();
21753    let snapshot = editor
21754        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21755        .unwrap();
21756    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
21757
21758    cx.update_window(*editor, |_, window, cx| {
21759        (render_args.callback)(false, window, cx)
21760    })
21761    .unwrap();
21762    let snapshot = editor
21763        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21764        .unwrap();
21765    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21766}
21767
21768#[gpui::test]
21769async fn test_input_text(cx: &mut TestAppContext) {
21770    init_test(cx, |_| {});
21771    let mut cx = EditorTestContext::new(cx).await;
21772
21773    cx.set_state(
21774        &r#"ˇone
21775        two
21776
21777        three
21778        fourˇ
21779        five
21780
21781        siˇx"#
21782            .unindent(),
21783    );
21784
21785    cx.dispatch_action(HandleInput(String::new()));
21786    cx.assert_editor_state(
21787        &r#"ˇone
21788        two
21789
21790        three
21791        fourˇ
21792        five
21793
21794        siˇx"#
21795            .unindent(),
21796    );
21797
21798    cx.dispatch_action(HandleInput("AAAA".to_string()));
21799    cx.assert_editor_state(
21800        &r#"AAAAˇone
21801        two
21802
21803        three
21804        fourAAAAˇ
21805        five
21806
21807        siAAAAˇx"#
21808            .unindent(),
21809    );
21810}
21811
21812#[gpui::test]
21813async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
21814    init_test(cx, |_| {});
21815
21816    let mut cx = EditorTestContext::new(cx).await;
21817    cx.set_state(
21818        r#"let foo = 1;
21819let foo = 2;
21820let foo = 3;
21821let fooˇ = 4;
21822let foo = 5;
21823let foo = 6;
21824let foo = 7;
21825let foo = 8;
21826let foo = 9;
21827let foo = 10;
21828let foo = 11;
21829let foo = 12;
21830let foo = 13;
21831let foo = 14;
21832let foo = 15;"#,
21833    );
21834
21835    cx.update_editor(|e, window, cx| {
21836        assert_eq!(
21837            e.next_scroll_position,
21838            NextScrollCursorCenterTopBottom::Center,
21839            "Default next scroll direction is center",
21840        );
21841
21842        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21843        assert_eq!(
21844            e.next_scroll_position,
21845            NextScrollCursorCenterTopBottom::Top,
21846            "After center, next scroll direction should be top",
21847        );
21848
21849        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21850        assert_eq!(
21851            e.next_scroll_position,
21852            NextScrollCursorCenterTopBottom::Bottom,
21853            "After top, next scroll direction should be bottom",
21854        );
21855
21856        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21857        assert_eq!(
21858            e.next_scroll_position,
21859            NextScrollCursorCenterTopBottom::Center,
21860            "After bottom, scrolling should start over",
21861        );
21862
21863        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21864        assert_eq!(
21865            e.next_scroll_position,
21866            NextScrollCursorCenterTopBottom::Top,
21867            "Scrolling continues if retriggered fast enough"
21868        );
21869    });
21870
21871    cx.executor()
21872        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
21873    cx.executor().run_until_parked();
21874    cx.update_editor(|e, _, _| {
21875        assert_eq!(
21876            e.next_scroll_position,
21877            NextScrollCursorCenterTopBottom::Center,
21878            "If scrolling is not triggered fast enough, it should reset"
21879        );
21880    });
21881}
21882
21883#[gpui::test]
21884async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
21885    init_test(cx, |_| {});
21886    let mut cx = EditorLspTestContext::new_rust(
21887        lsp::ServerCapabilities {
21888            definition_provider: Some(lsp::OneOf::Left(true)),
21889            references_provider: Some(lsp::OneOf::Left(true)),
21890            ..lsp::ServerCapabilities::default()
21891        },
21892        cx,
21893    )
21894    .await;
21895
21896    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
21897        let go_to_definition = cx
21898            .lsp
21899            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21900                move |params, _| async move {
21901                    if empty_go_to_definition {
21902                        Ok(None)
21903                    } else {
21904                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
21905                            uri: params.text_document_position_params.text_document.uri,
21906                            range: lsp::Range::new(
21907                                lsp::Position::new(4, 3),
21908                                lsp::Position::new(4, 6),
21909                            ),
21910                        })))
21911                    }
21912                },
21913            );
21914        let references = cx
21915            .lsp
21916            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21917                Ok(Some(vec![lsp::Location {
21918                    uri: params.text_document_position.text_document.uri,
21919                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
21920                }]))
21921            });
21922        (go_to_definition, references)
21923    };
21924
21925    cx.set_state(
21926        &r#"fn one() {
21927            let mut a = ˇtwo();
21928        }
21929
21930        fn two() {}"#
21931            .unindent(),
21932    );
21933    set_up_lsp_handlers(false, &mut cx);
21934    let navigated = cx
21935        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21936        .await
21937        .expect("Failed to navigate to definition");
21938    assert_eq!(
21939        navigated,
21940        Navigated::Yes,
21941        "Should have navigated to definition from the GetDefinition response"
21942    );
21943    cx.assert_editor_state(
21944        &r#"fn one() {
21945            let mut a = two();
21946        }
21947
21948        fn «twoˇ»() {}"#
21949            .unindent(),
21950    );
21951
21952    let editors = cx.update_workspace(|workspace, _, cx| {
21953        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21954    });
21955    cx.update_editor(|_, _, test_editor_cx| {
21956        assert_eq!(
21957            editors.len(),
21958            1,
21959            "Initially, only one, test, editor should be open in the workspace"
21960        );
21961        assert_eq!(
21962            test_editor_cx.entity(),
21963            editors.last().expect("Asserted len is 1").clone()
21964        );
21965    });
21966
21967    set_up_lsp_handlers(true, &mut cx);
21968    let navigated = cx
21969        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21970        .await
21971        .expect("Failed to navigate to lookup references");
21972    assert_eq!(
21973        navigated,
21974        Navigated::Yes,
21975        "Should have navigated to references as a fallback after empty GoToDefinition response"
21976    );
21977    // We should not change the selections in the existing file,
21978    // if opening another milti buffer with the references
21979    cx.assert_editor_state(
21980        &r#"fn one() {
21981            let mut a = two();
21982        }
21983
21984        fn «twoˇ»() {}"#
21985            .unindent(),
21986    );
21987    let editors = cx.update_workspace(|workspace, _, cx| {
21988        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21989    });
21990    cx.update_editor(|_, _, test_editor_cx| {
21991        assert_eq!(
21992            editors.len(),
21993            2,
21994            "After falling back to references search, we open a new editor with the results"
21995        );
21996        let references_fallback_text = editors
21997            .into_iter()
21998            .find(|new_editor| *new_editor != test_editor_cx.entity())
21999            .expect("Should have one non-test editor now")
22000            .read(test_editor_cx)
22001            .text(test_editor_cx);
22002        assert_eq!(
22003            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
22004            "Should use the range from the references response and not the GoToDefinition one"
22005        );
22006    });
22007}
22008
22009#[gpui::test]
22010async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
22011    init_test(cx, |_| {});
22012    cx.update(|cx| {
22013        let mut editor_settings = EditorSettings::get_global(cx).clone();
22014        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
22015        EditorSettings::override_global(editor_settings, cx);
22016    });
22017    let mut cx = EditorLspTestContext::new_rust(
22018        lsp::ServerCapabilities {
22019            definition_provider: Some(lsp::OneOf::Left(true)),
22020            references_provider: Some(lsp::OneOf::Left(true)),
22021            ..lsp::ServerCapabilities::default()
22022        },
22023        cx,
22024    )
22025    .await;
22026    let original_state = r#"fn one() {
22027        let mut a = ˇtwo();
22028    }
22029
22030    fn two() {}"#
22031        .unindent();
22032    cx.set_state(&original_state);
22033
22034    let mut go_to_definition = cx
22035        .lsp
22036        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
22037            move |_, _| async move { Ok(None) },
22038        );
22039    let _references = cx
22040        .lsp
22041        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
22042            panic!("Should not call for references with no go to definition fallback")
22043        });
22044
22045    let navigated = cx
22046        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
22047        .await
22048        .expect("Failed to navigate to lookup references");
22049    go_to_definition
22050        .next()
22051        .await
22052        .expect("Should have called the go_to_definition handler");
22053
22054    assert_eq!(
22055        navigated,
22056        Navigated::No,
22057        "Should have navigated to references as a fallback after empty GoToDefinition response"
22058    );
22059    cx.assert_editor_state(&original_state);
22060    let editors = cx.update_workspace(|workspace, _, cx| {
22061        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22062    });
22063    cx.update_editor(|_, _, _| {
22064        assert_eq!(
22065            editors.len(),
22066            1,
22067            "After unsuccessful fallback, no other editor should have been opened"
22068        );
22069    });
22070}
22071
22072#[gpui::test]
22073async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
22074    init_test(cx, |_| {});
22075    let mut cx = EditorLspTestContext::new_rust(
22076        lsp::ServerCapabilities {
22077            references_provider: Some(lsp::OneOf::Left(true)),
22078            ..lsp::ServerCapabilities::default()
22079        },
22080        cx,
22081    )
22082    .await;
22083
22084    cx.set_state(
22085        &r#"
22086        fn one() {
22087            let mut a = two();
22088        }
22089
22090        fn ˇtwo() {}"#
22091            .unindent(),
22092    );
22093    cx.lsp
22094        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
22095            Ok(Some(vec![
22096                lsp::Location {
22097                    uri: params.text_document_position.text_document.uri.clone(),
22098                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
22099                },
22100                lsp::Location {
22101                    uri: params.text_document_position.text_document.uri,
22102                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
22103                },
22104            ]))
22105        });
22106    let navigated = cx
22107        .update_editor(|editor, window, cx| {
22108            editor.find_all_references(&FindAllReferences, window, cx)
22109        })
22110        .unwrap()
22111        .await
22112        .expect("Failed to navigate to references");
22113    assert_eq!(
22114        navigated,
22115        Navigated::Yes,
22116        "Should have navigated to references from the FindAllReferences response"
22117    );
22118    cx.assert_editor_state(
22119        &r#"fn one() {
22120            let mut a = two();
22121        }
22122
22123        fn ˇtwo() {}"#
22124            .unindent(),
22125    );
22126
22127    let editors = cx.update_workspace(|workspace, _, cx| {
22128        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22129    });
22130    cx.update_editor(|_, _, _| {
22131        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
22132    });
22133
22134    cx.set_state(
22135        &r#"fn one() {
22136            let mut a = ˇtwo();
22137        }
22138
22139        fn two() {}"#
22140            .unindent(),
22141    );
22142    let navigated = cx
22143        .update_editor(|editor, window, cx| {
22144            editor.find_all_references(&FindAllReferences, window, cx)
22145        })
22146        .unwrap()
22147        .await
22148        .expect("Failed to navigate to references");
22149    assert_eq!(
22150        navigated,
22151        Navigated::Yes,
22152        "Should have navigated to references from the FindAllReferences response"
22153    );
22154    cx.assert_editor_state(
22155        &r#"fn one() {
22156            let mut a = ˇtwo();
22157        }
22158
22159        fn two() {}"#
22160            .unindent(),
22161    );
22162    let editors = cx.update_workspace(|workspace, _, cx| {
22163        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22164    });
22165    cx.update_editor(|_, _, _| {
22166        assert_eq!(
22167            editors.len(),
22168            2,
22169            "should have re-used the previous multibuffer"
22170        );
22171    });
22172
22173    cx.set_state(
22174        &r#"fn one() {
22175            let mut a = ˇtwo();
22176        }
22177        fn three() {}
22178        fn two() {}"#
22179            .unindent(),
22180    );
22181    cx.lsp
22182        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
22183            Ok(Some(vec![
22184                lsp::Location {
22185                    uri: params.text_document_position.text_document.uri.clone(),
22186                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
22187                },
22188                lsp::Location {
22189                    uri: params.text_document_position.text_document.uri,
22190                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
22191                },
22192            ]))
22193        });
22194    let navigated = cx
22195        .update_editor(|editor, window, cx| {
22196            editor.find_all_references(&FindAllReferences, window, cx)
22197        })
22198        .unwrap()
22199        .await
22200        .expect("Failed to navigate to references");
22201    assert_eq!(
22202        navigated,
22203        Navigated::Yes,
22204        "Should have navigated to references from the FindAllReferences response"
22205    );
22206    cx.assert_editor_state(
22207        &r#"fn one() {
22208                let mut a = ˇtwo();
22209            }
22210            fn three() {}
22211            fn two() {}"#
22212            .unindent(),
22213    );
22214    let editors = cx.update_workspace(|workspace, _, cx| {
22215        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22216    });
22217    cx.update_editor(|_, _, _| {
22218        assert_eq!(
22219            editors.len(),
22220            3,
22221            "should have used a new multibuffer as offsets changed"
22222        );
22223    });
22224}
22225#[gpui::test]
22226async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
22227    init_test(cx, |_| {});
22228
22229    let language = Arc::new(Language::new(
22230        LanguageConfig::default(),
22231        Some(tree_sitter_rust::LANGUAGE.into()),
22232    ));
22233
22234    let text = r#"
22235        #[cfg(test)]
22236        mod tests() {
22237            #[test]
22238            fn runnable_1() {
22239                let a = 1;
22240            }
22241
22242            #[test]
22243            fn runnable_2() {
22244                let a = 1;
22245                let b = 2;
22246            }
22247        }
22248    "#
22249    .unindent();
22250
22251    let fs = FakeFs::new(cx.executor());
22252    fs.insert_file("/file.rs", Default::default()).await;
22253
22254    let project = Project::test(fs, ["/a".as_ref()], cx).await;
22255    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22256    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22257    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
22258    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
22259
22260    let editor = cx.new_window_entity(|window, cx| {
22261        Editor::new(
22262            EditorMode::full(),
22263            multi_buffer,
22264            Some(project.clone()),
22265            window,
22266            cx,
22267        )
22268    });
22269
22270    editor.update_in(cx, |editor, window, cx| {
22271        let snapshot = editor.buffer().read(cx).snapshot(cx);
22272        editor.tasks.insert(
22273            (buffer.read(cx).remote_id(), 3),
22274            RunnableTasks {
22275                templates: vec![],
22276                offset: snapshot.anchor_before(43),
22277                column: 0,
22278                extra_variables: HashMap::default(),
22279                context_range: BufferOffset(43)..BufferOffset(85),
22280            },
22281        );
22282        editor.tasks.insert(
22283            (buffer.read(cx).remote_id(), 8),
22284            RunnableTasks {
22285                templates: vec![],
22286                offset: snapshot.anchor_before(86),
22287                column: 0,
22288                extra_variables: HashMap::default(),
22289                context_range: BufferOffset(86)..BufferOffset(191),
22290            },
22291        );
22292
22293        // Test finding task when cursor is inside function body
22294        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22295            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
22296        });
22297        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
22298        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
22299
22300        // Test finding task when cursor is on function name
22301        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22302            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
22303        });
22304        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
22305        assert_eq!(row, 8, "Should find task when cursor is on function name");
22306    });
22307}
22308
22309#[gpui::test]
22310async fn test_folding_buffers(cx: &mut TestAppContext) {
22311    init_test(cx, |_| {});
22312
22313    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
22314    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
22315    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
22316
22317    let fs = FakeFs::new(cx.executor());
22318    fs.insert_tree(
22319        path!("/a"),
22320        json!({
22321            "first.rs": sample_text_1,
22322            "second.rs": sample_text_2,
22323            "third.rs": sample_text_3,
22324        }),
22325    )
22326    .await;
22327    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22328    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22329    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22330    let worktree = project.update(cx, |project, cx| {
22331        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22332        assert_eq!(worktrees.len(), 1);
22333        worktrees.pop().unwrap()
22334    });
22335    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22336
22337    let buffer_1 = project
22338        .update(cx, |project, cx| {
22339            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
22340        })
22341        .await
22342        .unwrap();
22343    let buffer_2 = project
22344        .update(cx, |project, cx| {
22345            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
22346        })
22347        .await
22348        .unwrap();
22349    let buffer_3 = project
22350        .update(cx, |project, cx| {
22351            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
22352        })
22353        .await
22354        .unwrap();
22355
22356    let multi_buffer = cx.new(|cx| {
22357        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22358        multi_buffer.push_excerpts(
22359            buffer_1.clone(),
22360            [
22361                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22362                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22363                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22364            ],
22365            cx,
22366        );
22367        multi_buffer.push_excerpts(
22368            buffer_2.clone(),
22369            [
22370                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22371                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22372                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22373            ],
22374            cx,
22375        );
22376        multi_buffer.push_excerpts(
22377            buffer_3.clone(),
22378            [
22379                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22380                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22381                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22382            ],
22383            cx,
22384        );
22385        multi_buffer
22386    });
22387    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22388        Editor::new(
22389            EditorMode::full(),
22390            multi_buffer.clone(),
22391            Some(project.clone()),
22392            window,
22393            cx,
22394        )
22395    });
22396
22397    assert_eq!(
22398        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22399        "\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",
22400    );
22401
22402    multi_buffer_editor.update(cx, |editor, cx| {
22403        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
22404    });
22405    assert_eq!(
22406        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22407        "\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",
22408        "After folding the first buffer, its text should not be displayed"
22409    );
22410
22411    multi_buffer_editor.update(cx, |editor, cx| {
22412        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
22413    });
22414    assert_eq!(
22415        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22416        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
22417        "After folding the second buffer, its text should not be displayed"
22418    );
22419
22420    multi_buffer_editor.update(cx, |editor, cx| {
22421        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
22422    });
22423    assert_eq!(
22424        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22425        "\n\n\n\n\n",
22426        "After folding the third buffer, its text should not be displayed"
22427    );
22428
22429    // Emulate selection inside the fold logic, that should work
22430    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22431        editor
22432            .snapshot(window, cx)
22433            .next_line_boundary(Point::new(0, 4));
22434    });
22435
22436    multi_buffer_editor.update(cx, |editor, cx| {
22437        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
22438    });
22439    assert_eq!(
22440        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22441        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
22442        "After unfolding the second buffer, its text should be displayed"
22443    );
22444
22445    // Typing inside of buffer 1 causes that buffer to be unfolded.
22446    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22447        assert_eq!(
22448            multi_buffer
22449                .read(cx)
22450                .snapshot(cx)
22451                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
22452                .collect::<String>(),
22453            "bbbb"
22454        );
22455        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22456            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
22457        });
22458        editor.handle_input("B", window, cx);
22459    });
22460
22461    assert_eq!(
22462        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22463        "\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",
22464        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
22465    );
22466
22467    multi_buffer_editor.update(cx, |editor, cx| {
22468        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
22469    });
22470    assert_eq!(
22471        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22472        "\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",
22473        "After unfolding the all buffers, all original text should be displayed"
22474    );
22475}
22476
22477#[gpui::test]
22478async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
22479    init_test(cx, |_| {});
22480
22481    let sample_text_1 = "1111\n2222\n3333".to_string();
22482    let sample_text_2 = "4444\n5555\n6666".to_string();
22483    let sample_text_3 = "7777\n8888\n9999".to_string();
22484
22485    let fs = FakeFs::new(cx.executor());
22486    fs.insert_tree(
22487        path!("/a"),
22488        json!({
22489            "first.rs": sample_text_1,
22490            "second.rs": sample_text_2,
22491            "third.rs": sample_text_3,
22492        }),
22493    )
22494    .await;
22495    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22496    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22497    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22498    let worktree = project.update(cx, |project, cx| {
22499        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22500        assert_eq!(worktrees.len(), 1);
22501        worktrees.pop().unwrap()
22502    });
22503    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22504
22505    let buffer_1 = project
22506        .update(cx, |project, cx| {
22507            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
22508        })
22509        .await
22510        .unwrap();
22511    let buffer_2 = project
22512        .update(cx, |project, cx| {
22513            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
22514        })
22515        .await
22516        .unwrap();
22517    let buffer_3 = project
22518        .update(cx, |project, cx| {
22519            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
22520        })
22521        .await
22522        .unwrap();
22523
22524    let multi_buffer = cx.new(|cx| {
22525        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22526        multi_buffer.push_excerpts(
22527            buffer_1.clone(),
22528            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22529            cx,
22530        );
22531        multi_buffer.push_excerpts(
22532            buffer_2.clone(),
22533            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22534            cx,
22535        );
22536        multi_buffer.push_excerpts(
22537            buffer_3.clone(),
22538            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22539            cx,
22540        );
22541        multi_buffer
22542    });
22543
22544    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22545        Editor::new(
22546            EditorMode::full(),
22547            multi_buffer,
22548            Some(project.clone()),
22549            window,
22550            cx,
22551        )
22552    });
22553
22554    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
22555    assert_eq!(
22556        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22557        full_text,
22558    );
22559
22560    multi_buffer_editor.update(cx, |editor, cx| {
22561        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
22562    });
22563    assert_eq!(
22564        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22565        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
22566        "After folding the first buffer, its text should not be displayed"
22567    );
22568
22569    multi_buffer_editor.update(cx, |editor, cx| {
22570        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
22571    });
22572
22573    assert_eq!(
22574        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22575        "\n\n\n\n\n\n7777\n8888\n9999",
22576        "After folding the second buffer, its text should not be displayed"
22577    );
22578
22579    multi_buffer_editor.update(cx, |editor, cx| {
22580        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
22581    });
22582    assert_eq!(
22583        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22584        "\n\n\n\n\n",
22585        "After folding the third buffer, its text should not be displayed"
22586    );
22587
22588    multi_buffer_editor.update(cx, |editor, cx| {
22589        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
22590    });
22591    assert_eq!(
22592        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22593        "\n\n\n\n4444\n5555\n6666\n\n",
22594        "After unfolding the second buffer, its text should be displayed"
22595    );
22596
22597    multi_buffer_editor.update(cx, |editor, cx| {
22598        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
22599    });
22600    assert_eq!(
22601        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22602        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
22603        "After unfolding the first buffer, its text should be displayed"
22604    );
22605
22606    multi_buffer_editor.update(cx, |editor, cx| {
22607        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
22608    });
22609    assert_eq!(
22610        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22611        full_text,
22612        "After unfolding all buffers, all original text should be displayed"
22613    );
22614}
22615
22616#[gpui::test]
22617async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
22618    init_test(cx, |_| {});
22619
22620    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
22621
22622    let fs = FakeFs::new(cx.executor());
22623    fs.insert_tree(
22624        path!("/a"),
22625        json!({
22626            "main.rs": sample_text,
22627        }),
22628    )
22629    .await;
22630    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22631    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22632    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22633    let worktree = project.update(cx, |project, cx| {
22634        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22635        assert_eq!(worktrees.len(), 1);
22636        worktrees.pop().unwrap()
22637    });
22638    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22639
22640    let buffer_1 = project
22641        .update(cx, |project, cx| {
22642            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22643        })
22644        .await
22645        .unwrap();
22646
22647    let multi_buffer = cx.new(|cx| {
22648        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22649        multi_buffer.push_excerpts(
22650            buffer_1.clone(),
22651            [ExcerptRange::new(
22652                Point::new(0, 0)
22653                    ..Point::new(
22654                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
22655                        0,
22656                    ),
22657            )],
22658            cx,
22659        );
22660        multi_buffer
22661    });
22662    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22663        Editor::new(
22664            EditorMode::full(),
22665            multi_buffer,
22666            Some(project.clone()),
22667            window,
22668            cx,
22669        )
22670    });
22671
22672    let selection_range = Point::new(1, 0)..Point::new(2, 0);
22673    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22674        enum TestHighlight {}
22675        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
22676        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
22677        editor.highlight_text::<TestHighlight>(
22678            vec![highlight_range.clone()],
22679            HighlightStyle::color(Hsla::green()),
22680            cx,
22681        );
22682        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22683            s.select_ranges(Some(highlight_range))
22684        });
22685    });
22686
22687    let full_text = format!("\n\n{sample_text}");
22688    assert_eq!(
22689        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22690        full_text,
22691    );
22692}
22693
22694#[gpui::test]
22695async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
22696    init_test(cx, |_| {});
22697    cx.update(|cx| {
22698        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
22699            "keymaps/default-linux.json",
22700            cx,
22701        )
22702        .unwrap();
22703        cx.bind_keys(default_key_bindings);
22704    });
22705
22706    let (editor, cx) = cx.add_window_view(|window, cx| {
22707        let multi_buffer = MultiBuffer::build_multi(
22708            [
22709                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
22710                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
22711                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
22712                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
22713            ],
22714            cx,
22715        );
22716        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
22717
22718        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
22719        // fold all but the second buffer, so that we test navigating between two
22720        // adjacent folded buffers, as well as folded buffers at the start and
22721        // end the multibuffer
22722        editor.fold_buffer(buffer_ids[0], cx);
22723        editor.fold_buffer(buffer_ids[2], cx);
22724        editor.fold_buffer(buffer_ids[3], cx);
22725
22726        editor
22727    });
22728    cx.simulate_resize(size(px(1000.), px(1000.)));
22729
22730    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
22731    cx.assert_excerpts_with_selections(indoc! {"
22732        [EXCERPT]
22733        ˇ[FOLDED]
22734        [EXCERPT]
22735        a1
22736        b1
22737        [EXCERPT]
22738        [FOLDED]
22739        [EXCERPT]
22740        [FOLDED]
22741        "
22742    });
22743    cx.simulate_keystroke("down");
22744    cx.assert_excerpts_with_selections(indoc! {"
22745        [EXCERPT]
22746        [FOLDED]
22747        [EXCERPT]
22748        ˇa1
22749        b1
22750        [EXCERPT]
22751        [FOLDED]
22752        [EXCERPT]
22753        [FOLDED]
22754        "
22755    });
22756    cx.simulate_keystroke("down");
22757    cx.assert_excerpts_with_selections(indoc! {"
22758        [EXCERPT]
22759        [FOLDED]
22760        [EXCERPT]
22761        a1
22762        ˇb1
22763        [EXCERPT]
22764        [FOLDED]
22765        [EXCERPT]
22766        [FOLDED]
22767        "
22768    });
22769    cx.simulate_keystroke("down");
22770    cx.assert_excerpts_with_selections(indoc! {"
22771        [EXCERPT]
22772        [FOLDED]
22773        [EXCERPT]
22774        a1
22775        b1
22776        ˇ[EXCERPT]
22777        [FOLDED]
22778        [EXCERPT]
22779        [FOLDED]
22780        "
22781    });
22782    cx.simulate_keystroke("down");
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    for _ in 0..5 {
22796        cx.simulate_keystroke("down");
22797        cx.assert_excerpts_with_selections(indoc! {"
22798            [EXCERPT]
22799            [FOLDED]
22800            [EXCERPT]
22801            a1
22802            b1
22803            [EXCERPT]
22804            [FOLDED]
22805            [EXCERPT]
22806            ˇ[FOLDED]
22807            "
22808        });
22809    }
22810
22811    cx.simulate_keystroke("up");
22812    cx.assert_excerpts_with_selections(indoc! {"
22813        [EXCERPT]
22814        [FOLDED]
22815        [EXCERPT]
22816        a1
22817        b1
22818        [EXCERPT]
22819        ˇ[FOLDED]
22820        [EXCERPT]
22821        [FOLDED]
22822        "
22823    });
22824    cx.simulate_keystroke("up");
22825    cx.assert_excerpts_with_selections(indoc! {"
22826        [EXCERPT]
22827        [FOLDED]
22828        [EXCERPT]
22829        a1
22830        b1
22831        ˇ[EXCERPT]
22832        [FOLDED]
22833        [EXCERPT]
22834        [FOLDED]
22835        "
22836    });
22837    cx.simulate_keystroke("up");
22838    cx.assert_excerpts_with_selections(indoc! {"
22839        [EXCERPT]
22840        [FOLDED]
22841        [EXCERPT]
22842        a1
22843        ˇb1
22844        [EXCERPT]
22845        [FOLDED]
22846        [EXCERPT]
22847        [FOLDED]
22848        "
22849    });
22850    cx.simulate_keystroke("up");
22851    cx.assert_excerpts_with_selections(indoc! {"
22852        [EXCERPT]
22853        [FOLDED]
22854        [EXCERPT]
22855        ˇa1
22856        b1
22857        [EXCERPT]
22858        [FOLDED]
22859        [EXCERPT]
22860        [FOLDED]
22861        "
22862    });
22863    for _ in 0..5 {
22864        cx.simulate_keystroke("up");
22865        cx.assert_excerpts_with_selections(indoc! {"
22866            [EXCERPT]
22867            ˇ[FOLDED]
22868            [EXCERPT]
22869            a1
22870            b1
22871            [EXCERPT]
22872            [FOLDED]
22873            [EXCERPT]
22874            [FOLDED]
22875            "
22876        });
22877    }
22878}
22879
22880#[gpui::test]
22881async fn test_edit_prediction_text(cx: &mut TestAppContext) {
22882    init_test(cx, |_| {});
22883
22884    // Simple insertion
22885    assert_highlighted_edits(
22886        "Hello, world!",
22887        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
22888        true,
22889        cx,
22890        |highlighted_edits, cx| {
22891            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
22892            assert_eq!(highlighted_edits.highlights.len(), 1);
22893            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
22894            assert_eq!(
22895                highlighted_edits.highlights[0].1.background_color,
22896                Some(cx.theme().status().created_background)
22897            );
22898        },
22899    )
22900    .await;
22901
22902    // Replacement
22903    assert_highlighted_edits(
22904        "This is a test.",
22905        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
22906        false,
22907        cx,
22908        |highlighted_edits, cx| {
22909            assert_eq!(highlighted_edits.text, "That is a test.");
22910            assert_eq!(highlighted_edits.highlights.len(), 1);
22911            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
22912            assert_eq!(
22913                highlighted_edits.highlights[0].1.background_color,
22914                Some(cx.theme().status().created_background)
22915            );
22916        },
22917    )
22918    .await;
22919
22920    // Multiple edits
22921    assert_highlighted_edits(
22922        "Hello, world!",
22923        vec![
22924            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
22925            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
22926        ],
22927        false,
22928        cx,
22929        |highlighted_edits, cx| {
22930            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
22931            assert_eq!(highlighted_edits.highlights.len(), 2);
22932            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
22933            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
22934            assert_eq!(
22935                highlighted_edits.highlights[0].1.background_color,
22936                Some(cx.theme().status().created_background)
22937            );
22938            assert_eq!(
22939                highlighted_edits.highlights[1].1.background_color,
22940                Some(cx.theme().status().created_background)
22941            );
22942        },
22943    )
22944    .await;
22945
22946    // Multiple lines with edits
22947    assert_highlighted_edits(
22948        "First line\nSecond line\nThird line\nFourth line",
22949        vec![
22950            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
22951            (
22952                Point::new(2, 0)..Point::new(2, 10),
22953                "New third line".to_string(),
22954            ),
22955            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
22956        ],
22957        false,
22958        cx,
22959        |highlighted_edits, cx| {
22960            assert_eq!(
22961                highlighted_edits.text,
22962                "Second modified\nNew third line\nFourth updated line"
22963            );
22964            assert_eq!(highlighted_edits.highlights.len(), 3);
22965            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
22966            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
22967            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
22968            for highlight in &highlighted_edits.highlights {
22969                assert_eq!(
22970                    highlight.1.background_color,
22971                    Some(cx.theme().status().created_background)
22972                );
22973            }
22974        },
22975    )
22976    .await;
22977}
22978
22979#[gpui::test]
22980async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
22981    init_test(cx, |_| {});
22982
22983    // Deletion
22984    assert_highlighted_edits(
22985        "Hello, world!",
22986        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
22987        true,
22988        cx,
22989        |highlighted_edits, cx| {
22990            assert_eq!(highlighted_edits.text, "Hello, world!");
22991            assert_eq!(highlighted_edits.highlights.len(), 1);
22992            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
22993            assert_eq!(
22994                highlighted_edits.highlights[0].1.background_color,
22995                Some(cx.theme().status().deleted_background)
22996            );
22997        },
22998    )
22999    .await;
23000
23001    // Insertion
23002    assert_highlighted_edits(
23003        "Hello, world!",
23004        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
23005        true,
23006        cx,
23007        |highlighted_edits, cx| {
23008            assert_eq!(highlighted_edits.highlights.len(), 1);
23009            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
23010            assert_eq!(
23011                highlighted_edits.highlights[0].1.background_color,
23012                Some(cx.theme().status().created_background)
23013            );
23014        },
23015    )
23016    .await;
23017}
23018
23019async fn assert_highlighted_edits(
23020    text: &str,
23021    edits: Vec<(Range<Point>, String)>,
23022    include_deletions: bool,
23023    cx: &mut TestAppContext,
23024    assertion_fn: impl Fn(HighlightedText, &App),
23025) {
23026    let window = cx.add_window(|window, cx| {
23027        let buffer = MultiBuffer::build_simple(text, cx);
23028        Editor::new(EditorMode::full(), buffer, None, window, cx)
23029    });
23030    let cx = &mut VisualTestContext::from_window(*window, cx);
23031
23032    let (buffer, snapshot) = window
23033        .update(cx, |editor, _window, cx| {
23034            (
23035                editor.buffer().clone(),
23036                editor.buffer().read(cx).snapshot(cx),
23037            )
23038        })
23039        .unwrap();
23040
23041    let edits = edits
23042        .into_iter()
23043        .map(|(range, edit)| {
23044            (
23045                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
23046                edit,
23047            )
23048        })
23049        .collect::<Vec<_>>();
23050
23051    let text_anchor_edits = edits
23052        .clone()
23053        .into_iter()
23054        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit.into()))
23055        .collect::<Vec<_>>();
23056
23057    let edit_preview = window
23058        .update(cx, |_, _window, cx| {
23059            buffer
23060                .read(cx)
23061                .as_singleton()
23062                .unwrap()
23063                .read(cx)
23064                .preview_edits(text_anchor_edits.into(), cx)
23065        })
23066        .unwrap()
23067        .await;
23068
23069    cx.update(|_window, cx| {
23070        let highlighted_edits = edit_prediction_edit_text(
23071            snapshot.as_singleton().unwrap().2,
23072            &edits,
23073            &edit_preview,
23074            include_deletions,
23075            cx,
23076        );
23077        assertion_fn(highlighted_edits, cx)
23078    });
23079}
23080
23081#[track_caller]
23082fn assert_breakpoint(
23083    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
23084    path: &Arc<Path>,
23085    expected: Vec<(u32, Breakpoint)>,
23086) {
23087    if expected.is_empty() {
23088        assert!(!breakpoints.contains_key(path), "{}", path.display());
23089    } else {
23090        let mut breakpoint = breakpoints
23091            .get(path)
23092            .unwrap()
23093            .iter()
23094            .map(|breakpoint| {
23095                (
23096                    breakpoint.row,
23097                    Breakpoint {
23098                        message: breakpoint.message.clone(),
23099                        state: breakpoint.state,
23100                        condition: breakpoint.condition.clone(),
23101                        hit_condition: breakpoint.hit_condition.clone(),
23102                    },
23103                )
23104            })
23105            .collect::<Vec<_>>();
23106
23107        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
23108
23109        assert_eq!(expected, breakpoint);
23110    }
23111}
23112
23113fn add_log_breakpoint_at_cursor(
23114    editor: &mut Editor,
23115    log_message: &str,
23116    window: &mut Window,
23117    cx: &mut Context<Editor>,
23118) {
23119    let (anchor, bp) = editor
23120        .breakpoints_at_cursors(window, cx)
23121        .first()
23122        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
23123        .unwrap_or_else(|| {
23124            let snapshot = editor.snapshot(window, cx);
23125            let cursor_position: Point =
23126                editor.selections.newest(&snapshot.display_snapshot).head();
23127
23128            let breakpoint_position = snapshot
23129                .buffer_snapshot()
23130                .anchor_before(Point::new(cursor_position.row, 0));
23131
23132            (breakpoint_position, Breakpoint::new_log(log_message))
23133        });
23134
23135    editor.edit_breakpoint_at_anchor(
23136        anchor,
23137        bp,
23138        BreakpointEditAction::EditLogMessage(log_message.into()),
23139        cx,
23140    );
23141}
23142
23143#[gpui::test]
23144async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
23145    init_test(cx, |_| {});
23146
23147    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23148    let fs = FakeFs::new(cx.executor());
23149    fs.insert_tree(
23150        path!("/a"),
23151        json!({
23152            "main.rs": sample_text,
23153        }),
23154    )
23155    .await;
23156    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23157    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23158    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23159
23160    let fs = FakeFs::new(cx.executor());
23161    fs.insert_tree(
23162        path!("/a"),
23163        json!({
23164            "main.rs": sample_text,
23165        }),
23166    )
23167    .await;
23168    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23169    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23170    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23171    let worktree_id = workspace
23172        .update(cx, |workspace, _window, cx| {
23173            workspace.project().update(cx, |project, cx| {
23174                project.worktrees(cx).next().unwrap().read(cx).id()
23175            })
23176        })
23177        .unwrap();
23178
23179    let buffer = project
23180        .update(cx, |project, cx| {
23181            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23182        })
23183        .await
23184        .unwrap();
23185
23186    let (editor, cx) = cx.add_window_view(|window, cx| {
23187        Editor::new(
23188            EditorMode::full(),
23189            MultiBuffer::build_from_buffer(buffer, cx),
23190            Some(project.clone()),
23191            window,
23192            cx,
23193        )
23194    });
23195
23196    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23197    let abs_path = project.read_with(cx, |project, cx| {
23198        project
23199            .absolute_path(&project_path, cx)
23200            .map(Arc::from)
23201            .unwrap()
23202    });
23203
23204    // assert we can add breakpoint on the first line
23205    editor.update_in(cx, |editor, window, cx| {
23206        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23207        editor.move_to_end(&MoveToEnd, window, cx);
23208        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23209    });
23210
23211    let breakpoints = editor.update(cx, |editor, cx| {
23212        editor
23213            .breakpoint_store()
23214            .as_ref()
23215            .unwrap()
23216            .read(cx)
23217            .all_source_breakpoints(cx)
23218    });
23219
23220    assert_eq!(1, breakpoints.len());
23221    assert_breakpoint(
23222        &breakpoints,
23223        &abs_path,
23224        vec![
23225            (0, Breakpoint::new_standard()),
23226            (3, Breakpoint::new_standard()),
23227        ],
23228    );
23229
23230    editor.update_in(cx, |editor, window, cx| {
23231        editor.move_to_beginning(&MoveToBeginning, window, cx);
23232        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23233    });
23234
23235    let breakpoints = editor.update(cx, |editor, cx| {
23236        editor
23237            .breakpoint_store()
23238            .as_ref()
23239            .unwrap()
23240            .read(cx)
23241            .all_source_breakpoints(cx)
23242    });
23243
23244    assert_eq!(1, breakpoints.len());
23245    assert_breakpoint(
23246        &breakpoints,
23247        &abs_path,
23248        vec![(3, Breakpoint::new_standard())],
23249    );
23250
23251    editor.update_in(cx, |editor, window, cx| {
23252        editor.move_to_end(&MoveToEnd, window, cx);
23253        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23254    });
23255
23256    let breakpoints = editor.update(cx, |editor, cx| {
23257        editor
23258            .breakpoint_store()
23259            .as_ref()
23260            .unwrap()
23261            .read(cx)
23262            .all_source_breakpoints(cx)
23263    });
23264
23265    assert_eq!(0, breakpoints.len());
23266    assert_breakpoint(&breakpoints, &abs_path, vec![]);
23267}
23268
23269#[gpui::test]
23270async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
23271    init_test(cx, |_| {});
23272
23273    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23274
23275    let fs = FakeFs::new(cx.executor());
23276    fs.insert_tree(
23277        path!("/a"),
23278        json!({
23279            "main.rs": sample_text,
23280        }),
23281    )
23282    .await;
23283    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23284    let (workspace, cx) =
23285        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23286
23287    let worktree_id = workspace.update(cx, |workspace, cx| {
23288        workspace.project().update(cx, |project, cx| {
23289            project.worktrees(cx).next().unwrap().read(cx).id()
23290        })
23291    });
23292
23293    let buffer = project
23294        .update(cx, |project, cx| {
23295            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23296        })
23297        .await
23298        .unwrap();
23299
23300    let (editor, cx) = cx.add_window_view(|window, cx| {
23301        Editor::new(
23302            EditorMode::full(),
23303            MultiBuffer::build_from_buffer(buffer, cx),
23304            Some(project.clone()),
23305            window,
23306            cx,
23307        )
23308    });
23309
23310    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23311    let abs_path = project.read_with(cx, |project, cx| {
23312        project
23313            .absolute_path(&project_path, cx)
23314            .map(Arc::from)
23315            .unwrap()
23316    });
23317
23318    editor.update_in(cx, |editor, window, cx| {
23319        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
23320    });
23321
23322    let breakpoints = editor.update(cx, |editor, cx| {
23323        editor
23324            .breakpoint_store()
23325            .as_ref()
23326            .unwrap()
23327            .read(cx)
23328            .all_source_breakpoints(cx)
23329    });
23330
23331    assert_breakpoint(
23332        &breakpoints,
23333        &abs_path,
23334        vec![(0, Breakpoint::new_log("hello world"))],
23335    );
23336
23337    // Removing a log message from a log breakpoint should remove it
23338    editor.update_in(cx, |editor, window, cx| {
23339        add_log_breakpoint_at_cursor(editor, "", window, cx);
23340    });
23341
23342    let breakpoints = editor.update(cx, |editor, cx| {
23343        editor
23344            .breakpoint_store()
23345            .as_ref()
23346            .unwrap()
23347            .read(cx)
23348            .all_source_breakpoints(cx)
23349    });
23350
23351    assert_breakpoint(&breakpoints, &abs_path, vec![]);
23352
23353    editor.update_in(cx, |editor, window, cx| {
23354        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23355        editor.move_to_end(&MoveToEnd, window, cx);
23356        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23357        // Not adding a log message to a standard breakpoint shouldn't remove it
23358        add_log_breakpoint_at_cursor(editor, "", window, cx);
23359    });
23360
23361    let breakpoints = editor.update(cx, |editor, cx| {
23362        editor
23363            .breakpoint_store()
23364            .as_ref()
23365            .unwrap()
23366            .read(cx)
23367            .all_source_breakpoints(cx)
23368    });
23369
23370    assert_breakpoint(
23371        &breakpoints,
23372        &abs_path,
23373        vec![
23374            (0, Breakpoint::new_standard()),
23375            (3, Breakpoint::new_standard()),
23376        ],
23377    );
23378
23379    editor.update_in(cx, |editor, window, cx| {
23380        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
23381    });
23382
23383    let breakpoints = editor.update(cx, |editor, cx| {
23384        editor
23385            .breakpoint_store()
23386            .as_ref()
23387            .unwrap()
23388            .read(cx)
23389            .all_source_breakpoints(cx)
23390    });
23391
23392    assert_breakpoint(
23393        &breakpoints,
23394        &abs_path,
23395        vec![
23396            (0, Breakpoint::new_standard()),
23397            (3, Breakpoint::new_log("hello world")),
23398        ],
23399    );
23400
23401    editor.update_in(cx, |editor, window, cx| {
23402        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
23403    });
23404
23405    let breakpoints = editor.update(cx, |editor, cx| {
23406        editor
23407            .breakpoint_store()
23408            .as_ref()
23409            .unwrap()
23410            .read(cx)
23411            .all_source_breakpoints(cx)
23412    });
23413
23414    assert_breakpoint(
23415        &breakpoints,
23416        &abs_path,
23417        vec![
23418            (0, Breakpoint::new_standard()),
23419            (3, Breakpoint::new_log("hello Earth!!")),
23420        ],
23421    );
23422}
23423
23424/// This also tests that Editor::breakpoint_at_cursor_head is working properly
23425/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
23426/// or when breakpoints were placed out of order. This tests for a regression too
23427#[gpui::test]
23428async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
23429    init_test(cx, |_| {});
23430
23431    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23432    let fs = FakeFs::new(cx.executor());
23433    fs.insert_tree(
23434        path!("/a"),
23435        json!({
23436            "main.rs": sample_text,
23437        }),
23438    )
23439    .await;
23440    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23441    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23442    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23443
23444    let fs = FakeFs::new(cx.executor());
23445    fs.insert_tree(
23446        path!("/a"),
23447        json!({
23448            "main.rs": sample_text,
23449        }),
23450    )
23451    .await;
23452    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23453    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23454    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23455    let worktree_id = workspace
23456        .update(cx, |workspace, _window, cx| {
23457            workspace.project().update(cx, |project, cx| {
23458                project.worktrees(cx).next().unwrap().read(cx).id()
23459            })
23460        })
23461        .unwrap();
23462
23463    let buffer = project
23464        .update(cx, |project, cx| {
23465            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23466        })
23467        .await
23468        .unwrap();
23469
23470    let (editor, cx) = cx.add_window_view(|window, cx| {
23471        Editor::new(
23472            EditorMode::full(),
23473            MultiBuffer::build_from_buffer(buffer, cx),
23474            Some(project.clone()),
23475            window,
23476            cx,
23477        )
23478    });
23479
23480    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23481    let abs_path = project.read_with(cx, |project, cx| {
23482        project
23483            .absolute_path(&project_path, cx)
23484            .map(Arc::from)
23485            .unwrap()
23486    });
23487
23488    // assert we can add breakpoint on the first line
23489    editor.update_in(cx, |editor, window, cx| {
23490        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23491        editor.move_to_end(&MoveToEnd, window, cx);
23492        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23493        editor.move_up(&MoveUp, window, cx);
23494        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23495    });
23496
23497    let breakpoints = editor.update(cx, |editor, cx| {
23498        editor
23499            .breakpoint_store()
23500            .as_ref()
23501            .unwrap()
23502            .read(cx)
23503            .all_source_breakpoints(cx)
23504    });
23505
23506    assert_eq!(1, breakpoints.len());
23507    assert_breakpoint(
23508        &breakpoints,
23509        &abs_path,
23510        vec![
23511            (0, Breakpoint::new_standard()),
23512            (2, Breakpoint::new_standard()),
23513            (3, Breakpoint::new_standard()),
23514        ],
23515    );
23516
23517    editor.update_in(cx, |editor, window, cx| {
23518        editor.move_to_beginning(&MoveToBeginning, window, cx);
23519        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23520        editor.move_to_end(&MoveToEnd, window, cx);
23521        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23522        // Disabling a breakpoint that doesn't exist should do nothing
23523        editor.move_up(&MoveUp, window, cx);
23524        editor.move_up(&MoveUp, window, cx);
23525        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23526    });
23527
23528    let breakpoints = editor.update(cx, |editor, cx| {
23529        editor
23530            .breakpoint_store()
23531            .as_ref()
23532            .unwrap()
23533            .read(cx)
23534            .all_source_breakpoints(cx)
23535    });
23536
23537    let disable_breakpoint = {
23538        let mut bp = Breakpoint::new_standard();
23539        bp.state = BreakpointState::Disabled;
23540        bp
23541    };
23542
23543    assert_eq!(1, breakpoints.len());
23544    assert_breakpoint(
23545        &breakpoints,
23546        &abs_path,
23547        vec![
23548            (0, disable_breakpoint.clone()),
23549            (2, Breakpoint::new_standard()),
23550            (3, disable_breakpoint.clone()),
23551        ],
23552    );
23553
23554    editor.update_in(cx, |editor, window, cx| {
23555        editor.move_to_beginning(&MoveToBeginning, window, cx);
23556        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23557        editor.move_to_end(&MoveToEnd, window, cx);
23558        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23559        editor.move_up(&MoveUp, window, cx);
23560        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23561    });
23562
23563    let breakpoints = editor.update(cx, |editor, cx| {
23564        editor
23565            .breakpoint_store()
23566            .as_ref()
23567            .unwrap()
23568            .read(cx)
23569            .all_source_breakpoints(cx)
23570    });
23571
23572    assert_eq!(1, breakpoints.len());
23573    assert_breakpoint(
23574        &breakpoints,
23575        &abs_path,
23576        vec![
23577            (0, Breakpoint::new_standard()),
23578            (2, disable_breakpoint),
23579            (3, Breakpoint::new_standard()),
23580        ],
23581    );
23582}
23583
23584#[gpui::test]
23585async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
23586    init_test(cx, |_| {});
23587    let capabilities = lsp::ServerCapabilities {
23588        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
23589            prepare_provider: Some(true),
23590            work_done_progress_options: Default::default(),
23591        })),
23592        ..Default::default()
23593    };
23594    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23595
23596    cx.set_state(indoc! {"
23597        struct Fˇoo {}
23598    "});
23599
23600    cx.update_editor(|editor, _, cx| {
23601        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23602        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23603        editor.highlight_background::<DocumentHighlightRead>(
23604            &[highlight_range],
23605            |theme| theme.colors().editor_document_highlight_read_background,
23606            cx,
23607        );
23608    });
23609
23610    let mut prepare_rename_handler = cx
23611        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
23612            move |_, _, _| async move {
23613                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
23614                    start: lsp::Position {
23615                        line: 0,
23616                        character: 7,
23617                    },
23618                    end: lsp::Position {
23619                        line: 0,
23620                        character: 10,
23621                    },
23622                })))
23623            },
23624        );
23625    let prepare_rename_task = cx
23626        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23627        .expect("Prepare rename was not started");
23628    prepare_rename_handler.next().await.unwrap();
23629    prepare_rename_task.await.expect("Prepare rename failed");
23630
23631    let mut rename_handler =
23632        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23633            let edit = lsp::TextEdit {
23634                range: lsp::Range {
23635                    start: lsp::Position {
23636                        line: 0,
23637                        character: 7,
23638                    },
23639                    end: lsp::Position {
23640                        line: 0,
23641                        character: 10,
23642                    },
23643                },
23644                new_text: "FooRenamed".to_string(),
23645            };
23646            Ok(Some(lsp::WorkspaceEdit::new(
23647                // Specify the same edit twice
23648                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
23649            )))
23650        });
23651    let rename_task = cx
23652        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23653        .expect("Confirm rename was not started");
23654    rename_handler.next().await.unwrap();
23655    rename_task.await.expect("Confirm rename failed");
23656    cx.run_until_parked();
23657
23658    // Despite two edits, only one is actually applied as those are identical
23659    cx.assert_editor_state(indoc! {"
23660        struct FooRenamedˇ {}
23661    "});
23662}
23663
23664#[gpui::test]
23665async fn test_rename_without_prepare(cx: &mut TestAppContext) {
23666    init_test(cx, |_| {});
23667    // These capabilities indicate that the server does not support prepare rename.
23668    let capabilities = lsp::ServerCapabilities {
23669        rename_provider: Some(lsp::OneOf::Left(true)),
23670        ..Default::default()
23671    };
23672    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23673
23674    cx.set_state(indoc! {"
23675        struct Fˇoo {}
23676    "});
23677
23678    cx.update_editor(|editor, _window, cx| {
23679        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23680        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23681        editor.highlight_background::<DocumentHighlightRead>(
23682            &[highlight_range],
23683            |theme| theme.colors().editor_document_highlight_read_background,
23684            cx,
23685        );
23686    });
23687
23688    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23689        .expect("Prepare rename was not started")
23690        .await
23691        .expect("Prepare rename failed");
23692
23693    let mut rename_handler =
23694        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23695            let edit = lsp::TextEdit {
23696                range: lsp::Range {
23697                    start: lsp::Position {
23698                        line: 0,
23699                        character: 7,
23700                    },
23701                    end: lsp::Position {
23702                        line: 0,
23703                        character: 10,
23704                    },
23705                },
23706                new_text: "FooRenamed".to_string(),
23707            };
23708            Ok(Some(lsp::WorkspaceEdit::new(
23709                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
23710            )))
23711        });
23712    let rename_task = cx
23713        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23714        .expect("Confirm rename was not started");
23715    rename_handler.next().await.unwrap();
23716    rename_task.await.expect("Confirm rename failed");
23717    cx.run_until_parked();
23718
23719    // Correct range is renamed, as `surrounding_word` is used to find it.
23720    cx.assert_editor_state(indoc! {"
23721        struct FooRenamedˇ {}
23722    "});
23723}
23724
23725#[gpui::test]
23726async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
23727    init_test(cx, |_| {});
23728    let mut cx = EditorTestContext::new(cx).await;
23729
23730    let language = Arc::new(
23731        Language::new(
23732            LanguageConfig::default(),
23733            Some(tree_sitter_html::LANGUAGE.into()),
23734        )
23735        .with_brackets_query(
23736            r#"
23737            ("<" @open "/>" @close)
23738            ("</" @open ">" @close)
23739            ("<" @open ">" @close)
23740            ("\"" @open "\"" @close)
23741            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
23742        "#,
23743        )
23744        .unwrap(),
23745    );
23746    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23747
23748    cx.set_state(indoc! {"
23749        <span>ˇ</span>
23750    "});
23751    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23752    cx.assert_editor_state(indoc! {"
23753        <span>
23754        ˇ
23755        </span>
23756    "});
23757
23758    cx.set_state(indoc! {"
23759        <span><span></span>ˇ</span>
23760    "});
23761    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23762    cx.assert_editor_state(indoc! {"
23763        <span><span></span>
23764        ˇ</span>
23765    "});
23766
23767    cx.set_state(indoc! {"
23768        <span>ˇ
23769        </span>
23770    "});
23771    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23772    cx.assert_editor_state(indoc! {"
23773        <span>
23774        ˇ
23775        </span>
23776    "});
23777}
23778
23779#[gpui::test(iterations = 10)]
23780async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
23781    init_test(cx, |_| {});
23782
23783    let fs = FakeFs::new(cx.executor());
23784    fs.insert_tree(
23785        path!("/dir"),
23786        json!({
23787            "a.ts": "a",
23788        }),
23789    )
23790    .await;
23791
23792    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
23793    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23794    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23795
23796    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23797    language_registry.add(Arc::new(Language::new(
23798        LanguageConfig {
23799            name: "TypeScript".into(),
23800            matcher: LanguageMatcher {
23801                path_suffixes: vec!["ts".to_string()],
23802                ..Default::default()
23803            },
23804            ..Default::default()
23805        },
23806        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
23807    )));
23808    let mut fake_language_servers = language_registry.register_fake_lsp(
23809        "TypeScript",
23810        FakeLspAdapter {
23811            capabilities: lsp::ServerCapabilities {
23812                code_lens_provider: Some(lsp::CodeLensOptions {
23813                    resolve_provider: Some(true),
23814                }),
23815                execute_command_provider: Some(lsp::ExecuteCommandOptions {
23816                    commands: vec!["_the/command".to_string()],
23817                    ..lsp::ExecuteCommandOptions::default()
23818                }),
23819                ..lsp::ServerCapabilities::default()
23820            },
23821            ..FakeLspAdapter::default()
23822        },
23823    );
23824
23825    let editor = workspace
23826        .update(cx, |workspace, window, cx| {
23827            workspace.open_abs_path(
23828                PathBuf::from(path!("/dir/a.ts")),
23829                OpenOptions::default(),
23830                window,
23831                cx,
23832            )
23833        })
23834        .unwrap()
23835        .await
23836        .unwrap()
23837        .downcast::<Editor>()
23838        .unwrap();
23839    cx.executor().run_until_parked();
23840
23841    let fake_server = fake_language_servers.next().await.unwrap();
23842
23843    let buffer = editor.update(cx, |editor, cx| {
23844        editor
23845            .buffer()
23846            .read(cx)
23847            .as_singleton()
23848            .expect("have opened a single file by path")
23849    });
23850
23851    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
23852    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
23853    drop(buffer_snapshot);
23854    let actions = cx
23855        .update_window(*workspace, |_, window, cx| {
23856            project.code_actions(&buffer, anchor..anchor, window, cx)
23857        })
23858        .unwrap();
23859
23860    fake_server
23861        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23862            Ok(Some(vec![
23863                lsp::CodeLens {
23864                    range: lsp::Range::default(),
23865                    command: Some(lsp::Command {
23866                        title: "Code lens command".to_owned(),
23867                        command: "_the/command".to_owned(),
23868                        arguments: None,
23869                    }),
23870                    data: None,
23871                },
23872                lsp::CodeLens {
23873                    range: lsp::Range::default(),
23874                    command: Some(lsp::Command {
23875                        title: "Command not in capabilities".to_owned(),
23876                        command: "not in capabilities".to_owned(),
23877                        arguments: None,
23878                    }),
23879                    data: None,
23880                },
23881                lsp::CodeLens {
23882                    range: lsp::Range {
23883                        start: lsp::Position {
23884                            line: 1,
23885                            character: 1,
23886                        },
23887                        end: lsp::Position {
23888                            line: 1,
23889                            character: 1,
23890                        },
23891                    },
23892                    command: Some(lsp::Command {
23893                        title: "Command not in range".to_owned(),
23894                        command: "_the/command".to_owned(),
23895                        arguments: None,
23896                    }),
23897                    data: None,
23898                },
23899            ]))
23900        })
23901        .next()
23902        .await;
23903
23904    let actions = actions.await.unwrap();
23905    assert_eq!(
23906        actions.len(),
23907        1,
23908        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
23909    );
23910    let action = actions[0].clone();
23911    let apply = project.update(cx, |project, cx| {
23912        project.apply_code_action(buffer.clone(), action, true, cx)
23913    });
23914
23915    // Resolving the code action does not populate its edits. In absence of
23916    // edits, we must execute the given command.
23917    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
23918        |mut lens, _| async move {
23919            let lens_command = lens.command.as_mut().expect("should have a command");
23920            assert_eq!(lens_command.title, "Code lens command");
23921            lens_command.arguments = Some(vec![json!("the-argument")]);
23922            Ok(lens)
23923        },
23924    );
23925
23926    // While executing the command, the language server sends the editor
23927    // a `workspaceEdit` request.
23928    fake_server
23929        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
23930            let fake = fake_server.clone();
23931            move |params, _| {
23932                assert_eq!(params.command, "_the/command");
23933                let fake = fake.clone();
23934                async move {
23935                    fake.server
23936                        .request::<lsp::request::ApplyWorkspaceEdit>(
23937                            lsp::ApplyWorkspaceEditParams {
23938                                label: None,
23939                                edit: lsp::WorkspaceEdit {
23940                                    changes: Some(
23941                                        [(
23942                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
23943                                            vec![lsp::TextEdit {
23944                                                range: lsp::Range::new(
23945                                                    lsp::Position::new(0, 0),
23946                                                    lsp::Position::new(0, 0),
23947                                                ),
23948                                                new_text: "X".into(),
23949                                            }],
23950                                        )]
23951                                        .into_iter()
23952                                        .collect(),
23953                                    ),
23954                                    ..lsp::WorkspaceEdit::default()
23955                                },
23956                            },
23957                        )
23958                        .await
23959                        .into_response()
23960                        .unwrap();
23961                    Ok(Some(json!(null)))
23962                }
23963            }
23964        })
23965        .next()
23966        .await;
23967
23968    // Applying the code lens command returns a project transaction containing the edits
23969    // sent by the language server in its `workspaceEdit` request.
23970    let transaction = apply.await.unwrap();
23971    assert!(transaction.0.contains_key(&buffer));
23972    buffer.update(cx, |buffer, cx| {
23973        assert_eq!(buffer.text(), "Xa");
23974        buffer.undo(cx);
23975        assert_eq!(buffer.text(), "a");
23976    });
23977
23978    let actions_after_edits = cx
23979        .update_window(*workspace, |_, window, cx| {
23980            project.code_actions(&buffer, anchor..anchor, window, cx)
23981        })
23982        .unwrap()
23983        .await
23984        .unwrap();
23985    assert_eq!(
23986        actions, actions_after_edits,
23987        "For the same selection, same code lens actions should be returned"
23988    );
23989
23990    let _responses =
23991        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23992            panic!("No more code lens requests are expected");
23993        });
23994    editor.update_in(cx, |editor, window, cx| {
23995        editor.select_all(&SelectAll, window, cx);
23996    });
23997    cx.executor().run_until_parked();
23998    let new_actions = cx
23999        .update_window(*workspace, |_, window, cx| {
24000            project.code_actions(&buffer, anchor..anchor, window, cx)
24001        })
24002        .unwrap()
24003        .await
24004        .unwrap();
24005    assert_eq!(
24006        actions, new_actions,
24007        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
24008    );
24009}
24010
24011#[gpui::test]
24012async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
24013    init_test(cx, |_| {});
24014
24015    let fs = FakeFs::new(cx.executor());
24016    let main_text = r#"fn main() {
24017println!("1");
24018println!("2");
24019println!("3");
24020println!("4");
24021println!("5");
24022}"#;
24023    let lib_text = "mod foo {}";
24024    fs.insert_tree(
24025        path!("/a"),
24026        json!({
24027            "lib.rs": lib_text,
24028            "main.rs": main_text,
24029        }),
24030    )
24031    .await;
24032
24033    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24034    let (workspace, cx) =
24035        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24036    let worktree_id = workspace.update(cx, |workspace, cx| {
24037        workspace.project().update(cx, |project, cx| {
24038            project.worktrees(cx).next().unwrap().read(cx).id()
24039        })
24040    });
24041
24042    let expected_ranges = vec![
24043        Point::new(0, 0)..Point::new(0, 0),
24044        Point::new(1, 0)..Point::new(1, 1),
24045        Point::new(2, 0)..Point::new(2, 2),
24046        Point::new(3, 0)..Point::new(3, 3),
24047    ];
24048
24049    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24050    let editor_1 = workspace
24051        .update_in(cx, |workspace, window, cx| {
24052            workspace.open_path(
24053                (worktree_id, rel_path("main.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.update(cx, |pane, cx| {
24065        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24066        open_editor.update(cx, |editor, cx| {
24067            assert_eq!(
24068                editor.display_text(cx),
24069                main_text,
24070                "Original main.rs text on initial open",
24071            );
24072            assert_eq!(
24073                editor
24074                    .selections
24075                    .all::<Point>(&editor.display_snapshot(cx))
24076                    .into_iter()
24077                    .map(|s| s.range())
24078                    .collect::<Vec<_>>(),
24079                vec![Point::zero()..Point::zero()],
24080                "Default selections on initial open",
24081            );
24082        })
24083    });
24084    editor_1.update_in(cx, |editor, window, cx| {
24085        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24086            s.select_ranges(expected_ranges.clone());
24087        });
24088    });
24089
24090    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
24091        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
24092    });
24093    let editor_2 = workspace
24094        .update_in(cx, |workspace, window, cx| {
24095            workspace.open_path(
24096                (worktree_id, rel_path("main.rs")),
24097                Some(pane_2.downgrade()),
24098                true,
24099                window,
24100                cx,
24101            )
24102        })
24103        .unwrap()
24104        .await
24105        .downcast::<Editor>()
24106        .unwrap();
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                main_text,
24113                "Original main.rs text on initial open in another panel",
24114            );
24115            assert_eq!(
24116                editor
24117                    .selections
24118                    .all::<Point>(&editor.display_snapshot(cx))
24119                    .into_iter()
24120                    .map(|s| s.range())
24121                    .collect::<Vec<_>>(),
24122                vec![Point::zero()..Point::zero()],
24123                "Default selections on initial open in another panel",
24124            );
24125        })
24126    });
24127
24128    editor_2.update_in(cx, |editor, window, cx| {
24129        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
24130    });
24131
24132    let _other_editor_1 = workspace
24133        .update_in(cx, |workspace, window, cx| {
24134            workspace.open_path(
24135                (worktree_id, rel_path("lib.rs")),
24136                Some(pane_1.downgrade()),
24137                true,
24138                window,
24139                cx,
24140            )
24141        })
24142        .unwrap()
24143        .await
24144        .downcast::<Editor>()
24145        .unwrap();
24146    pane_1
24147        .update_in(cx, |pane, window, cx| {
24148            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
24149        })
24150        .await
24151        .unwrap();
24152    drop(editor_1);
24153    pane_1.update(cx, |pane, cx| {
24154        pane.active_item()
24155            .unwrap()
24156            .downcast::<Editor>()
24157            .unwrap()
24158            .update(cx, |editor, cx| {
24159                assert_eq!(
24160                    editor.display_text(cx),
24161                    lib_text,
24162                    "Other file should be open and active",
24163                );
24164            });
24165        assert_eq!(pane.items().count(), 1, "No other editors should be open");
24166    });
24167
24168    let _other_editor_2 = workspace
24169        .update_in(cx, |workspace, window, cx| {
24170            workspace.open_path(
24171                (worktree_id, rel_path("lib.rs")),
24172                Some(pane_2.downgrade()),
24173                true,
24174                window,
24175                cx,
24176            )
24177        })
24178        .unwrap()
24179        .await
24180        .downcast::<Editor>()
24181        .unwrap();
24182    pane_2
24183        .update_in(cx, |pane, window, cx| {
24184            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
24185        })
24186        .await
24187        .unwrap();
24188    drop(editor_2);
24189    pane_2.update(cx, |pane, cx| {
24190        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24191        open_editor.update(cx, |editor, cx| {
24192            assert_eq!(
24193                editor.display_text(cx),
24194                lib_text,
24195                "Other file should be open and active in another panel too",
24196            );
24197        });
24198        assert_eq!(
24199            pane.items().count(),
24200            1,
24201            "No other editors should be open in another pane",
24202        );
24203    });
24204
24205    let _editor_1_reopened = workspace
24206        .update_in(cx, |workspace, window, cx| {
24207            workspace.open_path(
24208                (worktree_id, rel_path("main.rs")),
24209                Some(pane_1.downgrade()),
24210                true,
24211                window,
24212                cx,
24213            )
24214        })
24215        .unwrap()
24216        .await
24217        .downcast::<Editor>()
24218        .unwrap();
24219    let _editor_2_reopened = workspace
24220        .update_in(cx, |workspace, window, cx| {
24221            workspace.open_path(
24222                (worktree_id, rel_path("main.rs")),
24223                Some(pane_2.downgrade()),
24224                true,
24225                window,
24226                cx,
24227            )
24228        })
24229        .unwrap()
24230        .await
24231        .downcast::<Editor>()
24232        .unwrap();
24233    pane_1.update(cx, |pane, cx| {
24234        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24235        open_editor.update(cx, |editor, cx| {
24236            assert_eq!(
24237                editor.display_text(cx),
24238                main_text,
24239                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
24240            );
24241            assert_eq!(
24242                editor
24243                    .selections
24244                    .all::<Point>(&editor.display_snapshot(cx))
24245                    .into_iter()
24246                    .map(|s| s.range())
24247                    .collect::<Vec<_>>(),
24248                expected_ranges,
24249                "Previous editor in the 1st panel had selections and should get them restored on reopen",
24250            );
24251        })
24252    });
24253    pane_2.update(cx, |pane, cx| {
24254        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24255        open_editor.update(cx, |editor, cx| {
24256            assert_eq!(
24257                editor.display_text(cx),
24258                r#"fn main() {
24259⋯rintln!("1");
24260⋯intln!("2");
24261⋯ntln!("3");
24262println!("4");
24263println!("5");
24264}"#,
24265                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
24266            );
24267            assert_eq!(
24268                editor
24269                    .selections
24270                    .all::<Point>(&editor.display_snapshot(cx))
24271                    .into_iter()
24272                    .map(|s| s.range())
24273                    .collect::<Vec<_>>(),
24274                vec![Point::zero()..Point::zero()],
24275                "Previous editor in the 2nd pane had no selections changed hence should restore none",
24276            );
24277        })
24278    });
24279}
24280
24281#[gpui::test]
24282async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
24283    init_test(cx, |_| {});
24284
24285    let fs = FakeFs::new(cx.executor());
24286    let main_text = r#"fn main() {
24287println!("1");
24288println!("2");
24289println!("3");
24290println!("4");
24291println!("5");
24292}"#;
24293    let lib_text = "mod foo {}";
24294    fs.insert_tree(
24295        path!("/a"),
24296        json!({
24297            "lib.rs": lib_text,
24298            "main.rs": main_text,
24299        }),
24300    )
24301    .await;
24302
24303    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24304    let (workspace, cx) =
24305        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24306    let worktree_id = workspace.update(cx, |workspace, cx| {
24307        workspace.project().update(cx, |project, cx| {
24308            project.worktrees(cx).next().unwrap().read(cx).id()
24309        })
24310    });
24311
24312    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24313    let editor = workspace
24314        .update_in(cx, |workspace, window, cx| {
24315            workspace.open_path(
24316                (worktree_id, rel_path("main.rs")),
24317                Some(pane.downgrade()),
24318                true,
24319                window,
24320                cx,
24321            )
24322        })
24323        .unwrap()
24324        .await
24325        .downcast::<Editor>()
24326        .unwrap();
24327    pane.update(cx, |pane, cx| {
24328        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24329        open_editor.update(cx, |editor, cx| {
24330            assert_eq!(
24331                editor.display_text(cx),
24332                main_text,
24333                "Original main.rs text on initial open",
24334            );
24335        })
24336    });
24337    editor.update_in(cx, |editor, window, cx| {
24338        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
24339    });
24340
24341    cx.update_global(|store: &mut SettingsStore, cx| {
24342        store.update_user_settings(cx, |s| {
24343            s.workspace.restore_on_file_reopen = Some(false);
24344        });
24345    });
24346    editor.update_in(cx, |editor, window, cx| {
24347        editor.fold_ranges(
24348            vec![
24349                Point::new(1, 0)..Point::new(1, 1),
24350                Point::new(2, 0)..Point::new(2, 2),
24351                Point::new(3, 0)..Point::new(3, 3),
24352            ],
24353            false,
24354            window,
24355            cx,
24356        );
24357    });
24358    pane.update_in(cx, |pane, window, cx| {
24359        pane.close_all_items(&CloseAllItems::default(), window, cx)
24360    })
24361    .await
24362    .unwrap();
24363    pane.update(cx, |pane, _| {
24364        assert!(pane.active_item().is_none());
24365    });
24366    cx.update_global(|store: &mut SettingsStore, cx| {
24367        store.update_user_settings(cx, |s| {
24368            s.workspace.restore_on_file_reopen = Some(true);
24369        });
24370    });
24371
24372    let _editor_reopened = workspace
24373        .update_in(cx, |workspace, window, cx| {
24374            workspace.open_path(
24375                (worktree_id, rel_path("main.rs")),
24376                Some(pane.downgrade()),
24377                true,
24378                window,
24379                cx,
24380            )
24381        })
24382        .unwrap()
24383        .await
24384        .downcast::<Editor>()
24385        .unwrap();
24386    pane.update(cx, |pane, cx| {
24387        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24388        open_editor.update(cx, |editor, cx| {
24389            assert_eq!(
24390                editor.display_text(cx),
24391                main_text,
24392                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
24393            );
24394        })
24395    });
24396}
24397
24398#[gpui::test]
24399async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
24400    struct EmptyModalView {
24401        focus_handle: gpui::FocusHandle,
24402    }
24403    impl EventEmitter<DismissEvent> for EmptyModalView {}
24404    impl Render for EmptyModalView {
24405        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
24406            div()
24407        }
24408    }
24409    impl Focusable for EmptyModalView {
24410        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
24411            self.focus_handle.clone()
24412        }
24413    }
24414    impl workspace::ModalView for EmptyModalView {}
24415    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
24416        EmptyModalView {
24417            focus_handle: cx.focus_handle(),
24418        }
24419    }
24420
24421    init_test(cx, |_| {});
24422
24423    let fs = FakeFs::new(cx.executor());
24424    let project = Project::test(fs, [], cx).await;
24425    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24426    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
24427    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24428    let editor = cx.new_window_entity(|window, cx| {
24429        Editor::new(
24430            EditorMode::full(),
24431            buffer,
24432            Some(project.clone()),
24433            window,
24434            cx,
24435        )
24436    });
24437    workspace
24438        .update(cx, |workspace, window, cx| {
24439            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
24440        })
24441        .unwrap();
24442    editor.update_in(cx, |editor, window, cx| {
24443        editor.open_context_menu(&OpenContextMenu, window, cx);
24444        assert!(editor.mouse_context_menu.is_some());
24445    });
24446    workspace
24447        .update(cx, |workspace, window, cx| {
24448            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
24449        })
24450        .unwrap();
24451    cx.read(|cx| {
24452        assert!(editor.read(cx).mouse_context_menu.is_none());
24453    });
24454}
24455
24456fn set_linked_edit_ranges(
24457    opening: (Point, Point),
24458    closing: (Point, Point),
24459    editor: &mut Editor,
24460    cx: &mut Context<Editor>,
24461) {
24462    let Some((buffer, _)) = editor
24463        .buffer
24464        .read(cx)
24465        .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
24466    else {
24467        panic!("Failed to get buffer for selection position");
24468    };
24469    let buffer = buffer.read(cx);
24470    let buffer_id = buffer.remote_id();
24471    let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
24472    let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
24473    let mut linked_ranges = HashMap::default();
24474    linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
24475    editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
24476}
24477
24478#[gpui::test]
24479async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
24480    init_test(cx, |_| {});
24481
24482    let fs = FakeFs::new(cx.executor());
24483    fs.insert_file(path!("/file.html"), Default::default())
24484        .await;
24485
24486    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
24487
24488    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24489    let html_language = Arc::new(Language::new(
24490        LanguageConfig {
24491            name: "HTML".into(),
24492            matcher: LanguageMatcher {
24493                path_suffixes: vec!["html".to_string()],
24494                ..LanguageMatcher::default()
24495            },
24496            brackets: BracketPairConfig {
24497                pairs: vec![BracketPair {
24498                    start: "<".into(),
24499                    end: ">".into(),
24500                    close: true,
24501                    ..Default::default()
24502                }],
24503                ..Default::default()
24504            },
24505            ..Default::default()
24506        },
24507        Some(tree_sitter_html::LANGUAGE.into()),
24508    ));
24509    language_registry.add(html_language);
24510    let mut fake_servers = language_registry.register_fake_lsp(
24511        "HTML",
24512        FakeLspAdapter {
24513            capabilities: lsp::ServerCapabilities {
24514                completion_provider: Some(lsp::CompletionOptions {
24515                    resolve_provider: Some(true),
24516                    ..Default::default()
24517                }),
24518                ..Default::default()
24519            },
24520            ..Default::default()
24521        },
24522    );
24523
24524    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24525    let cx = &mut VisualTestContext::from_window(*workspace, cx);
24526
24527    let worktree_id = workspace
24528        .update(cx, |workspace, _window, cx| {
24529            workspace.project().update(cx, |project, cx| {
24530                project.worktrees(cx).next().unwrap().read(cx).id()
24531            })
24532        })
24533        .unwrap();
24534    project
24535        .update(cx, |project, cx| {
24536            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
24537        })
24538        .await
24539        .unwrap();
24540    let editor = workspace
24541        .update(cx, |workspace, window, cx| {
24542            workspace.open_path((worktree_id, rel_path("file.html")), None, true, window, cx)
24543        })
24544        .unwrap()
24545        .await
24546        .unwrap()
24547        .downcast::<Editor>()
24548        .unwrap();
24549
24550    let fake_server = fake_servers.next().await.unwrap();
24551    editor.update_in(cx, |editor, window, cx| {
24552        editor.set_text("<ad></ad>", window, cx);
24553        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24554            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
24555        });
24556        set_linked_edit_ranges(
24557            (Point::new(0, 1), Point::new(0, 3)),
24558            (Point::new(0, 6), Point::new(0, 8)),
24559            editor,
24560            cx,
24561        );
24562    });
24563    let mut completion_handle =
24564        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
24565            Ok(Some(lsp::CompletionResponse::Array(vec![
24566                lsp::CompletionItem {
24567                    label: "head".to_string(),
24568                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
24569                        lsp::InsertReplaceEdit {
24570                            new_text: "head".to_string(),
24571                            insert: lsp::Range::new(
24572                                lsp::Position::new(0, 1),
24573                                lsp::Position::new(0, 3),
24574                            ),
24575                            replace: lsp::Range::new(
24576                                lsp::Position::new(0, 1),
24577                                lsp::Position::new(0, 3),
24578                            ),
24579                        },
24580                    )),
24581                    ..Default::default()
24582                },
24583            ])))
24584        });
24585    editor.update_in(cx, |editor, window, cx| {
24586        editor.show_completions(&ShowCompletions, window, cx);
24587    });
24588    cx.run_until_parked();
24589    completion_handle.next().await.unwrap();
24590    editor.update(cx, |editor, _| {
24591        assert!(
24592            editor.context_menu_visible(),
24593            "Completion menu should be visible"
24594        );
24595    });
24596    editor.update_in(cx, |editor, window, cx| {
24597        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
24598    });
24599    cx.executor().run_until_parked();
24600    editor.update(cx, |editor, cx| {
24601        assert_eq!(editor.text(cx), "<head></head>");
24602    });
24603}
24604
24605#[gpui::test]
24606async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
24607    init_test(cx, |_| {});
24608
24609    let mut cx = EditorTestContext::new(cx).await;
24610    let language = Arc::new(Language::new(
24611        LanguageConfig {
24612            name: "TSX".into(),
24613            matcher: LanguageMatcher {
24614                path_suffixes: vec!["tsx".to_string()],
24615                ..LanguageMatcher::default()
24616            },
24617            brackets: BracketPairConfig {
24618                pairs: vec![BracketPair {
24619                    start: "<".into(),
24620                    end: ">".into(),
24621                    close: true,
24622                    ..Default::default()
24623                }],
24624                ..Default::default()
24625            },
24626            linked_edit_characters: HashSet::from_iter(['.']),
24627            ..Default::default()
24628        },
24629        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
24630    ));
24631    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24632
24633    // Test typing > does not extend linked pair
24634    cx.set_state("<divˇ<div></div>");
24635    cx.update_editor(|editor, _, cx| {
24636        set_linked_edit_ranges(
24637            (Point::new(0, 1), Point::new(0, 4)),
24638            (Point::new(0, 11), Point::new(0, 14)),
24639            editor,
24640            cx,
24641        );
24642    });
24643    cx.update_editor(|editor, window, cx| {
24644        editor.handle_input(">", window, cx);
24645    });
24646    cx.assert_editor_state("<div>ˇ<div></div>");
24647
24648    // Test typing . do extend linked pair
24649    cx.set_state("<Animatedˇ></Animated>");
24650    cx.update_editor(|editor, _, cx| {
24651        set_linked_edit_ranges(
24652            (Point::new(0, 1), Point::new(0, 9)),
24653            (Point::new(0, 12), Point::new(0, 20)),
24654            editor,
24655            cx,
24656        );
24657    });
24658    cx.update_editor(|editor, window, cx| {
24659        editor.handle_input(".", window, cx);
24660    });
24661    cx.assert_editor_state("<Animated.ˇ></Animated.>");
24662    cx.update_editor(|editor, _, cx| {
24663        set_linked_edit_ranges(
24664            (Point::new(0, 1), Point::new(0, 10)),
24665            (Point::new(0, 13), Point::new(0, 21)),
24666            editor,
24667            cx,
24668        );
24669    });
24670    cx.update_editor(|editor, window, cx| {
24671        editor.handle_input("V", window, cx);
24672    });
24673    cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
24674}
24675
24676#[gpui::test]
24677async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
24678    init_test(cx, |_| {});
24679
24680    let fs = FakeFs::new(cx.executor());
24681    fs.insert_tree(
24682        path!("/root"),
24683        json!({
24684            "a": {
24685                "main.rs": "fn main() {}",
24686            },
24687            "foo": {
24688                "bar": {
24689                    "external_file.rs": "pub mod external {}",
24690                }
24691            }
24692        }),
24693    )
24694    .await;
24695
24696    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
24697    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24698    language_registry.add(rust_lang());
24699    let _fake_servers = language_registry.register_fake_lsp(
24700        "Rust",
24701        FakeLspAdapter {
24702            ..FakeLspAdapter::default()
24703        },
24704    );
24705    let (workspace, cx) =
24706        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24707    let worktree_id = workspace.update(cx, |workspace, cx| {
24708        workspace.project().update(cx, |project, cx| {
24709            project.worktrees(cx).next().unwrap().read(cx).id()
24710        })
24711    });
24712
24713    let assert_language_servers_count =
24714        |expected: usize, context: &str, cx: &mut VisualTestContext| {
24715            project.update(cx, |project, cx| {
24716                let current = project
24717                    .lsp_store()
24718                    .read(cx)
24719                    .as_local()
24720                    .unwrap()
24721                    .language_servers
24722                    .len();
24723                assert_eq!(expected, current, "{context}");
24724            });
24725        };
24726
24727    assert_language_servers_count(
24728        0,
24729        "No servers should be running before any file is open",
24730        cx,
24731    );
24732    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24733    let main_editor = workspace
24734        .update_in(cx, |workspace, window, cx| {
24735            workspace.open_path(
24736                (worktree_id, rel_path("main.rs")),
24737                Some(pane.downgrade()),
24738                true,
24739                window,
24740                cx,
24741            )
24742        })
24743        .unwrap()
24744        .await
24745        .downcast::<Editor>()
24746        .unwrap();
24747    pane.update(cx, |pane, cx| {
24748        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24749        open_editor.update(cx, |editor, cx| {
24750            assert_eq!(
24751                editor.display_text(cx),
24752                "fn main() {}",
24753                "Original main.rs text on initial open",
24754            );
24755        });
24756        assert_eq!(open_editor, main_editor);
24757    });
24758    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
24759
24760    let external_editor = workspace
24761        .update_in(cx, |workspace, window, cx| {
24762            workspace.open_abs_path(
24763                PathBuf::from("/root/foo/bar/external_file.rs"),
24764                OpenOptions::default(),
24765                window,
24766                cx,
24767            )
24768        })
24769        .await
24770        .expect("opening external file")
24771        .downcast::<Editor>()
24772        .expect("downcasted external file's open element to editor");
24773    pane.update(cx, |pane, cx| {
24774        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24775        open_editor.update(cx, |editor, cx| {
24776            assert_eq!(
24777                editor.display_text(cx),
24778                "pub mod external {}",
24779                "External file is open now",
24780            );
24781        });
24782        assert_eq!(open_editor, external_editor);
24783    });
24784    assert_language_servers_count(
24785        1,
24786        "Second, external, *.rs file should join the existing server",
24787        cx,
24788    );
24789
24790    pane.update_in(cx, |pane, window, cx| {
24791        pane.close_active_item(&CloseActiveItem::default(), window, cx)
24792    })
24793    .await
24794    .unwrap();
24795    pane.update_in(cx, |pane, window, cx| {
24796        pane.navigate_backward(&Default::default(), window, cx);
24797    });
24798    cx.run_until_parked();
24799    pane.update(cx, |pane, cx| {
24800        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24801        open_editor.update(cx, |editor, cx| {
24802            assert_eq!(
24803                editor.display_text(cx),
24804                "pub mod external {}",
24805                "External file is open now",
24806            );
24807        });
24808    });
24809    assert_language_servers_count(
24810        1,
24811        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
24812        cx,
24813    );
24814
24815    cx.update(|_, cx| {
24816        workspace::reload(cx);
24817    });
24818    assert_language_servers_count(
24819        1,
24820        "After reloading the worktree with local and external files opened, only one project should be started",
24821        cx,
24822    );
24823}
24824
24825#[gpui::test]
24826async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
24827    init_test(cx, |_| {});
24828
24829    let mut cx = EditorTestContext::new(cx).await;
24830    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24831    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24832
24833    // test cursor move to start of each line on tab
24834    // for `if`, `elif`, `else`, `while`, `with` and `for`
24835    cx.set_state(indoc! {"
24836        def main():
24837        ˇ    for item in items:
24838        ˇ        while item.active:
24839        ˇ            if item.value > 10:
24840        ˇ                continue
24841        ˇ            elif item.value < 0:
24842        ˇ                break
24843        ˇ            else:
24844        ˇ                with item.context() as ctx:
24845        ˇ                    yield count
24846        ˇ        else:
24847        ˇ            log('while else')
24848        ˇ    else:
24849        ˇ        log('for else')
24850    "});
24851    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24852    cx.assert_editor_state(indoc! {"
24853        def main():
24854            ˇfor item in items:
24855                ˇwhile item.active:
24856                    ˇif item.value > 10:
24857                        ˇcontinue
24858                    ˇelif item.value < 0:
24859                        ˇbreak
24860                    ˇelse:
24861                        ˇwith item.context() as ctx:
24862                            ˇyield count
24863                ˇelse:
24864                    ˇlog('while else')
24865            ˇelse:
24866                ˇlog('for else')
24867    "});
24868    // test relative indent is preserved when tab
24869    // for `if`, `elif`, `else`, `while`, `with` and `for`
24870    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24871    cx.assert_editor_state(indoc! {"
24872        def main():
24873                ˇfor item in items:
24874                    ˇwhile item.active:
24875                        ˇif item.value > 10:
24876                            ˇcontinue
24877                        ˇelif item.value < 0:
24878                            ˇbreak
24879                        ˇelse:
24880                            ˇwith item.context() as ctx:
24881                                ˇyield count
24882                    ˇelse:
24883                        ˇlog('while else')
24884                ˇelse:
24885                    ˇlog('for else')
24886    "});
24887
24888    // test cursor move to start of each line on tab
24889    // for `try`, `except`, `else`, `finally`, `match` and `def`
24890    cx.set_state(indoc! {"
24891        def main():
24892        ˇ    try:
24893        ˇ        fetch()
24894        ˇ    except ValueError:
24895        ˇ        handle_error()
24896        ˇ    else:
24897        ˇ        match value:
24898        ˇ            case _:
24899        ˇ    finally:
24900        ˇ        def status():
24901        ˇ            return 0
24902    "});
24903    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24904    cx.assert_editor_state(indoc! {"
24905        def main():
24906            ˇtry:
24907                ˇfetch()
24908            ˇexcept ValueError:
24909                ˇhandle_error()
24910            ˇelse:
24911                ˇmatch value:
24912                    ˇcase _:
24913            ˇfinally:
24914                ˇdef status():
24915                    ˇreturn 0
24916    "});
24917    // test relative indent is preserved when tab
24918    // for `try`, `except`, `else`, `finally`, `match` and `def`
24919    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24920    cx.assert_editor_state(indoc! {"
24921        def main():
24922                ˇtry:
24923                    ˇfetch()
24924                ˇexcept ValueError:
24925                    ˇhandle_error()
24926                ˇelse:
24927                    ˇmatch value:
24928                        ˇcase _:
24929                ˇfinally:
24930                    ˇdef status():
24931                        ˇreturn 0
24932    "});
24933}
24934
24935#[gpui::test]
24936async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
24937    init_test(cx, |_| {});
24938
24939    let mut cx = EditorTestContext::new(cx).await;
24940    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24941    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24942
24943    // test `else` auto outdents when typed inside `if` block
24944    cx.set_state(indoc! {"
24945        def main():
24946            if i == 2:
24947                return
24948                ˇ
24949    "});
24950    cx.update_editor(|editor, window, cx| {
24951        editor.handle_input("else:", window, cx);
24952    });
24953    cx.assert_editor_state(indoc! {"
24954        def main():
24955            if i == 2:
24956                return
24957            else:ˇ
24958    "});
24959
24960    // test `except` auto outdents when typed inside `try` block
24961    cx.set_state(indoc! {"
24962        def main():
24963            try:
24964                i = 2
24965                ˇ
24966    "});
24967    cx.update_editor(|editor, window, cx| {
24968        editor.handle_input("except:", window, cx);
24969    });
24970    cx.assert_editor_state(indoc! {"
24971        def main():
24972            try:
24973                i = 2
24974            except:ˇ
24975    "});
24976
24977    // test `else` auto outdents when typed inside `except` block
24978    cx.set_state(indoc! {"
24979        def main():
24980            try:
24981                i = 2
24982            except:
24983                j = 2
24984                ˇ
24985    "});
24986    cx.update_editor(|editor, window, cx| {
24987        editor.handle_input("else:", window, cx);
24988    });
24989    cx.assert_editor_state(indoc! {"
24990        def main():
24991            try:
24992                i = 2
24993            except:
24994                j = 2
24995            else:ˇ
24996    "});
24997
24998    // test `finally` auto outdents when typed inside `else` block
24999    cx.set_state(indoc! {"
25000        def main():
25001            try:
25002                i = 2
25003            except:
25004                j = 2
25005            else:
25006                k = 2
25007                ˇ
25008    "});
25009    cx.update_editor(|editor, window, cx| {
25010        editor.handle_input("finally:", window, cx);
25011    });
25012    cx.assert_editor_state(indoc! {"
25013        def main():
25014            try:
25015                i = 2
25016            except:
25017                j = 2
25018            else:
25019                k = 2
25020            finally:ˇ
25021    "});
25022
25023    // test `else` does not outdents when typed inside `except` block right after for block
25024    cx.set_state(indoc! {"
25025        def main():
25026            try:
25027                i = 2
25028            except:
25029                for i in range(n):
25030                    pass
25031                ˇ
25032    "});
25033    cx.update_editor(|editor, window, cx| {
25034        editor.handle_input("else:", window, cx);
25035    });
25036    cx.assert_editor_state(indoc! {"
25037        def main():
25038            try:
25039                i = 2
25040            except:
25041                for i in range(n):
25042                    pass
25043                else:ˇ
25044    "});
25045
25046    // test `finally` auto outdents when typed inside `else` block right after for block
25047    cx.set_state(indoc! {"
25048        def main():
25049            try:
25050                i = 2
25051            except:
25052                j = 2
25053            else:
25054                for i in range(n):
25055                    pass
25056                ˇ
25057    "});
25058    cx.update_editor(|editor, window, cx| {
25059        editor.handle_input("finally:", window, cx);
25060    });
25061    cx.assert_editor_state(indoc! {"
25062        def main():
25063            try:
25064                i = 2
25065            except:
25066                j = 2
25067            else:
25068                for i in range(n):
25069                    pass
25070            finally:ˇ
25071    "});
25072
25073    // test `except` outdents to inner "try" block
25074    cx.set_state(indoc! {"
25075        def main():
25076            try:
25077                i = 2
25078                if i == 2:
25079                    try:
25080                        i = 3
25081                        ˇ
25082    "});
25083    cx.update_editor(|editor, window, cx| {
25084        editor.handle_input("except:", window, cx);
25085    });
25086    cx.assert_editor_state(indoc! {"
25087        def main():
25088            try:
25089                i = 2
25090                if i == 2:
25091                    try:
25092                        i = 3
25093                    except:ˇ
25094    "});
25095
25096    // test `except` outdents to outer "try" block
25097    cx.set_state(indoc! {"
25098        def main():
25099            try:
25100                i = 2
25101                if i == 2:
25102                    try:
25103                        i = 3
25104                ˇ
25105    "});
25106    cx.update_editor(|editor, window, cx| {
25107        editor.handle_input("except:", window, cx);
25108    });
25109    cx.assert_editor_state(indoc! {"
25110        def main():
25111            try:
25112                i = 2
25113                if i == 2:
25114                    try:
25115                        i = 3
25116            except:ˇ
25117    "});
25118
25119    // test `else` stays at correct indent when typed after `for` block
25120    cx.set_state(indoc! {"
25121        def main():
25122            for i in range(10):
25123                if i == 3:
25124                    break
25125            ˇ
25126    "});
25127    cx.update_editor(|editor, window, cx| {
25128        editor.handle_input("else:", window, cx);
25129    });
25130    cx.assert_editor_state(indoc! {"
25131        def main():
25132            for i in range(10):
25133                if i == 3:
25134                    break
25135            else:ˇ
25136    "});
25137
25138    // test does not outdent on typing after line with square brackets
25139    cx.set_state(indoc! {"
25140        def f() -> list[str]:
25141            ˇ
25142    "});
25143    cx.update_editor(|editor, window, cx| {
25144        editor.handle_input("a", window, cx);
25145    });
25146    cx.assert_editor_state(indoc! {"
25147        def f() -> list[str]:
2514825149    "});
25150
25151    // test does not outdent on typing : after case keyword
25152    cx.set_state(indoc! {"
25153        match 1:
25154            caseˇ
25155    "});
25156    cx.update_editor(|editor, window, cx| {
25157        editor.handle_input(":", window, cx);
25158    });
25159    cx.assert_editor_state(indoc! {"
25160        match 1:
25161            case:ˇ
25162    "});
25163}
25164
25165#[gpui::test]
25166async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
25167    init_test(cx, |_| {});
25168    update_test_language_settings(cx, |settings| {
25169        settings.defaults.extend_comment_on_newline = Some(false);
25170    });
25171    let mut cx = EditorTestContext::new(cx).await;
25172    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
25173    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25174
25175    // test correct indent after newline on comment
25176    cx.set_state(indoc! {"
25177        # COMMENT:ˇ
25178    "});
25179    cx.update_editor(|editor, window, cx| {
25180        editor.newline(&Newline, window, cx);
25181    });
25182    cx.assert_editor_state(indoc! {"
25183        # COMMENT:
25184        ˇ
25185    "});
25186
25187    // test correct indent after newline in brackets
25188    cx.set_state(indoc! {"
25189        {ˇ}
25190    "});
25191    cx.update_editor(|editor, window, cx| {
25192        editor.newline(&Newline, window, cx);
25193    });
25194    cx.run_until_parked();
25195    cx.assert_editor_state(indoc! {"
25196        {
25197            ˇ
25198        }
25199    "});
25200
25201    cx.set_state(indoc! {"
25202        (ˇ)
25203    "});
25204    cx.update_editor(|editor, window, cx| {
25205        editor.newline(&Newline, window, cx);
25206    });
25207    cx.run_until_parked();
25208    cx.assert_editor_state(indoc! {"
25209        (
25210            ˇ
25211        )
25212    "});
25213
25214    // do not indent after empty lists or dictionaries
25215    cx.set_state(indoc! {"
25216        a = []ˇ
25217    "});
25218    cx.update_editor(|editor, window, cx| {
25219        editor.newline(&Newline, window, cx);
25220    });
25221    cx.run_until_parked();
25222    cx.assert_editor_state(indoc! {"
25223        a = []
25224        ˇ
25225    "});
25226}
25227
25228#[gpui::test]
25229async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
25230    init_test(cx, |_| {});
25231
25232    let mut cx = EditorTestContext::new(cx).await;
25233    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25234    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25235
25236    // test cursor move to start of each line on tab
25237    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
25238    cx.set_state(indoc! {"
25239        function main() {
25240        ˇ    for item in $items; do
25241        ˇ        while [ -n \"$item\" ]; do
25242        ˇ            if [ \"$value\" -gt 10 ]; then
25243        ˇ                continue
25244        ˇ            elif [ \"$value\" -lt 0 ]; then
25245        ˇ                break
25246        ˇ            else
25247        ˇ                echo \"$item\"
25248        ˇ            fi
25249        ˇ        done
25250        ˇ    done
25251        ˇ}
25252    "});
25253    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25254    cx.assert_editor_state(indoc! {"
25255        function main() {
25256            ˇfor item in $items; do
25257                ˇwhile [ -n \"$item\" ]; do
25258                    ˇif [ \"$value\" -gt 10 ]; then
25259                        ˇcontinue
25260                    ˇelif [ \"$value\" -lt 0 ]; then
25261                        ˇbreak
25262                    ˇelse
25263                        ˇecho \"$item\"
25264                    ˇfi
25265                ˇdone
25266            ˇdone
25267        ˇ}
25268    "});
25269    // test relative indent is preserved when tab
25270    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25271    cx.assert_editor_state(indoc! {"
25272        function main() {
25273                ˇfor item in $items; do
25274                    ˇwhile [ -n \"$item\" ]; do
25275                        ˇif [ \"$value\" -gt 10 ]; then
25276                            ˇcontinue
25277                        ˇelif [ \"$value\" -lt 0 ]; then
25278                            ˇbreak
25279                        ˇelse
25280                            ˇecho \"$item\"
25281                        ˇfi
25282                    ˇdone
25283                ˇdone
25284            ˇ}
25285    "});
25286
25287    // test cursor move to start of each line on tab
25288    // for `case` statement with patterns
25289    cx.set_state(indoc! {"
25290        function handle() {
25291        ˇ    case \"$1\" in
25292        ˇ        start)
25293        ˇ            echo \"a\"
25294        ˇ            ;;
25295        ˇ        stop)
25296        ˇ            echo \"b\"
25297        ˇ            ;;
25298        ˇ        *)
25299        ˇ            echo \"c\"
25300        ˇ            ;;
25301        ˇ    esac
25302        ˇ}
25303    "});
25304    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25305    cx.assert_editor_state(indoc! {"
25306        function handle() {
25307            ˇcase \"$1\" in
25308                ˇstart)
25309                    ˇecho \"a\"
25310                    ˇ;;
25311                ˇstop)
25312                    ˇecho \"b\"
25313                    ˇ;;
25314                ˇ*)
25315                    ˇecho \"c\"
25316                    ˇ;;
25317            ˇesac
25318        ˇ}
25319    "});
25320}
25321
25322#[gpui::test]
25323async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
25324    init_test(cx, |_| {});
25325
25326    let mut cx = EditorTestContext::new(cx).await;
25327    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25328    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25329
25330    // test indents on comment insert
25331    cx.set_state(indoc! {"
25332        function main() {
25333        ˇ    for item in $items; do
25334        ˇ        while [ -n \"$item\" ]; do
25335        ˇ            if [ \"$value\" -gt 10 ]; then
25336        ˇ                continue
25337        ˇ            elif [ \"$value\" -lt 0 ]; then
25338        ˇ                break
25339        ˇ            else
25340        ˇ                echo \"$item\"
25341        ˇ            fi
25342        ˇ        done
25343        ˇ    done
25344        ˇ}
25345    "});
25346    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
25347    cx.assert_editor_state(indoc! {"
25348        function main() {
25349        #ˇ    for item in $items; do
25350        #ˇ        while [ -n \"$item\" ]; do
25351        #ˇ            if [ \"$value\" -gt 10 ]; then
25352        #ˇ                continue
25353        #ˇ            elif [ \"$value\" -lt 0 ]; then
25354        #ˇ                break
25355        #ˇ            else
25356        #ˇ                echo \"$item\"
25357        #ˇ            fi
25358        #ˇ        done
25359        #ˇ    done
25360        #ˇ}
25361    "});
25362}
25363
25364#[gpui::test]
25365async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
25366    init_test(cx, |_| {});
25367
25368    let mut cx = EditorTestContext::new(cx).await;
25369    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25370    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25371
25372    // test `else` auto outdents when typed inside `if` block
25373    cx.set_state(indoc! {"
25374        if [ \"$1\" = \"test\" ]; then
25375            echo \"foo bar\"
25376            ˇ
25377    "});
25378    cx.update_editor(|editor, window, cx| {
25379        editor.handle_input("else", window, cx);
25380    });
25381    cx.assert_editor_state(indoc! {"
25382        if [ \"$1\" = \"test\" ]; then
25383            echo \"foo bar\"
25384        elseˇ
25385    "});
25386
25387    // test `elif` auto outdents when typed inside `if` block
25388    cx.set_state(indoc! {"
25389        if [ \"$1\" = \"test\" ]; then
25390            echo \"foo bar\"
25391            ˇ
25392    "});
25393    cx.update_editor(|editor, window, cx| {
25394        editor.handle_input("elif", window, cx);
25395    });
25396    cx.assert_editor_state(indoc! {"
25397        if [ \"$1\" = \"test\" ]; then
25398            echo \"foo bar\"
25399        elifˇ
25400    "});
25401
25402    // test `fi` auto outdents when typed inside `else` block
25403    cx.set_state(indoc! {"
25404        if [ \"$1\" = \"test\" ]; then
25405            echo \"foo bar\"
25406        else
25407            echo \"bar baz\"
25408            ˇ
25409    "});
25410    cx.update_editor(|editor, window, cx| {
25411        editor.handle_input("fi", window, cx);
25412    });
25413    cx.assert_editor_state(indoc! {"
25414        if [ \"$1\" = \"test\" ]; then
25415            echo \"foo bar\"
25416        else
25417            echo \"bar baz\"
25418        fiˇ
25419    "});
25420
25421    // test `done` auto outdents when typed inside `while` block
25422    cx.set_state(indoc! {"
25423        while read line; do
25424            echo \"$line\"
25425            ˇ
25426    "});
25427    cx.update_editor(|editor, window, cx| {
25428        editor.handle_input("done", window, cx);
25429    });
25430    cx.assert_editor_state(indoc! {"
25431        while read line; do
25432            echo \"$line\"
25433        doneˇ
25434    "});
25435
25436    // test `done` auto outdents when typed inside `for` block
25437    cx.set_state(indoc! {"
25438        for file in *.txt; do
25439            cat \"$file\"
25440            ˇ
25441    "});
25442    cx.update_editor(|editor, window, cx| {
25443        editor.handle_input("done", window, cx);
25444    });
25445    cx.assert_editor_state(indoc! {"
25446        for file in *.txt; do
25447            cat \"$file\"
25448        doneˇ
25449    "});
25450
25451    // test `esac` auto outdents when typed inside `case` block
25452    cx.set_state(indoc! {"
25453        case \"$1\" in
25454            start)
25455                echo \"foo bar\"
25456                ;;
25457            stop)
25458                echo \"bar baz\"
25459                ;;
25460            ˇ
25461    "});
25462    cx.update_editor(|editor, window, cx| {
25463        editor.handle_input("esac", window, cx);
25464    });
25465    cx.assert_editor_state(indoc! {"
25466        case \"$1\" in
25467            start)
25468                echo \"foo bar\"
25469                ;;
25470            stop)
25471                echo \"bar baz\"
25472                ;;
25473        esacˇ
25474    "});
25475
25476    // test `*)` auto outdents when typed inside `case` block
25477    cx.set_state(indoc! {"
25478        case \"$1\" in
25479            start)
25480                echo \"foo bar\"
25481                ;;
25482                ˇ
25483    "});
25484    cx.update_editor(|editor, window, cx| {
25485        editor.handle_input("*)", window, cx);
25486    });
25487    cx.assert_editor_state(indoc! {"
25488        case \"$1\" in
25489            start)
25490                echo \"foo bar\"
25491                ;;
25492            *)ˇ
25493    "});
25494
25495    // test `fi` outdents to correct level with nested if blocks
25496    cx.set_state(indoc! {"
25497        if [ \"$1\" = \"test\" ]; then
25498            echo \"outer if\"
25499            if [ \"$2\" = \"debug\" ]; then
25500                echo \"inner if\"
25501                ˇ
25502    "});
25503    cx.update_editor(|editor, window, cx| {
25504        editor.handle_input("fi", window, cx);
25505    });
25506    cx.assert_editor_state(indoc! {"
25507        if [ \"$1\" = \"test\" ]; then
25508            echo \"outer if\"
25509            if [ \"$2\" = \"debug\" ]; then
25510                echo \"inner if\"
25511            fiˇ
25512    "});
25513}
25514
25515#[gpui::test]
25516async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
25517    init_test(cx, |_| {});
25518    update_test_language_settings(cx, |settings| {
25519        settings.defaults.extend_comment_on_newline = Some(false);
25520    });
25521    let mut cx = EditorTestContext::new(cx).await;
25522    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25523    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25524
25525    // test correct indent after newline on comment
25526    cx.set_state(indoc! {"
25527        # COMMENT:ˇ
25528    "});
25529    cx.update_editor(|editor, window, cx| {
25530        editor.newline(&Newline, window, cx);
25531    });
25532    cx.assert_editor_state(indoc! {"
25533        # COMMENT:
25534        ˇ
25535    "});
25536
25537    // test correct indent after newline after `then`
25538    cx.set_state(indoc! {"
25539
25540        if [ \"$1\" = \"test\" ]; thenˇ
25541    "});
25542    cx.update_editor(|editor, window, cx| {
25543        editor.newline(&Newline, window, cx);
25544    });
25545    cx.run_until_parked();
25546    cx.assert_editor_state(indoc! {"
25547
25548        if [ \"$1\" = \"test\" ]; then
25549            ˇ
25550    "});
25551
25552    // test correct indent after newline after `else`
25553    cx.set_state(indoc! {"
25554        if [ \"$1\" = \"test\" ]; then
25555        elseˇ
25556    "});
25557    cx.update_editor(|editor, window, cx| {
25558        editor.newline(&Newline, window, cx);
25559    });
25560    cx.run_until_parked();
25561    cx.assert_editor_state(indoc! {"
25562        if [ \"$1\" = \"test\" ]; then
25563        else
25564            ˇ
25565    "});
25566
25567    // test correct indent after newline after `elif`
25568    cx.set_state(indoc! {"
25569        if [ \"$1\" = \"test\" ]; then
25570        elifˇ
25571    "});
25572    cx.update_editor(|editor, window, cx| {
25573        editor.newline(&Newline, window, cx);
25574    });
25575    cx.run_until_parked();
25576    cx.assert_editor_state(indoc! {"
25577        if [ \"$1\" = \"test\" ]; then
25578        elif
25579            ˇ
25580    "});
25581
25582    // test correct indent after newline after `do`
25583    cx.set_state(indoc! {"
25584        for file in *.txt; doˇ
25585    "});
25586    cx.update_editor(|editor, window, cx| {
25587        editor.newline(&Newline, window, cx);
25588    });
25589    cx.run_until_parked();
25590    cx.assert_editor_state(indoc! {"
25591        for file in *.txt; do
25592            ˇ
25593    "});
25594
25595    // test correct indent after newline after case pattern
25596    cx.set_state(indoc! {"
25597        case \"$1\" in
25598            start)ˇ
25599    "});
25600    cx.update_editor(|editor, window, cx| {
25601        editor.newline(&Newline, window, cx);
25602    });
25603    cx.run_until_parked();
25604    cx.assert_editor_state(indoc! {"
25605        case \"$1\" in
25606            start)
25607                ˇ
25608    "});
25609
25610    // test correct indent after newline after case pattern
25611    cx.set_state(indoc! {"
25612        case \"$1\" in
25613            start)
25614                ;;
25615            *)ˇ
25616    "});
25617    cx.update_editor(|editor, window, cx| {
25618        editor.newline(&Newline, window, cx);
25619    });
25620    cx.run_until_parked();
25621    cx.assert_editor_state(indoc! {"
25622        case \"$1\" in
25623            start)
25624                ;;
25625            *)
25626                ˇ
25627    "});
25628
25629    // test correct indent after newline after function opening brace
25630    cx.set_state(indoc! {"
25631        function test() {ˇ}
25632    "});
25633    cx.update_editor(|editor, window, cx| {
25634        editor.newline(&Newline, window, cx);
25635    });
25636    cx.run_until_parked();
25637    cx.assert_editor_state(indoc! {"
25638        function test() {
25639            ˇ
25640        }
25641    "});
25642
25643    // test no extra indent after semicolon on same line
25644    cx.set_state(indoc! {"
25645        echo \"test\"25646    "});
25647    cx.update_editor(|editor, window, cx| {
25648        editor.newline(&Newline, window, cx);
25649    });
25650    cx.run_until_parked();
25651    cx.assert_editor_state(indoc! {"
25652        echo \"test\";
25653        ˇ
25654    "});
25655}
25656
25657fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
25658    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
25659    point..point
25660}
25661
25662#[track_caller]
25663fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
25664    let (text, ranges) = marked_text_ranges(marked_text, true);
25665    assert_eq!(editor.text(cx), text);
25666    assert_eq!(
25667        editor.selections.ranges(&editor.display_snapshot(cx)),
25668        ranges,
25669        "Assert selections are {}",
25670        marked_text
25671    );
25672}
25673
25674pub fn handle_signature_help_request(
25675    cx: &mut EditorLspTestContext,
25676    mocked_response: lsp::SignatureHelp,
25677) -> impl Future<Output = ()> + use<> {
25678    let mut request =
25679        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
25680            let mocked_response = mocked_response.clone();
25681            async move { Ok(Some(mocked_response)) }
25682        });
25683
25684    async move {
25685        request.next().await;
25686    }
25687}
25688
25689#[track_caller]
25690pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
25691    cx.update_editor(|editor, _, _| {
25692        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
25693            let entries = menu.entries.borrow();
25694            let entries = entries
25695                .iter()
25696                .map(|entry| entry.string.as_str())
25697                .collect::<Vec<_>>();
25698            assert_eq!(entries, expected);
25699        } else {
25700            panic!("Expected completions menu");
25701        }
25702    });
25703}
25704
25705#[gpui::test]
25706async fn test_mixed_completions_with_multi_word_snippet(cx: &mut TestAppContext) {
25707    init_test(cx, |_| {});
25708    let mut cx = EditorLspTestContext::new_rust(
25709        lsp::ServerCapabilities {
25710            completion_provider: Some(lsp::CompletionOptions {
25711                ..Default::default()
25712            }),
25713            ..Default::default()
25714        },
25715        cx,
25716    )
25717    .await;
25718    cx.lsp
25719        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
25720            Ok(Some(lsp::CompletionResponse::Array(vec![
25721                lsp::CompletionItem {
25722                    label: "unsafe".into(),
25723                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
25724                        range: lsp::Range {
25725                            start: lsp::Position {
25726                                line: 0,
25727                                character: 9,
25728                            },
25729                            end: lsp::Position {
25730                                line: 0,
25731                                character: 11,
25732                            },
25733                        },
25734                        new_text: "unsafe".to_string(),
25735                    })),
25736                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
25737                    ..Default::default()
25738                },
25739            ])))
25740        });
25741
25742    cx.update_editor(|editor, _, cx| {
25743        editor.project().unwrap().update(cx, |project, cx| {
25744            project.snippets().update(cx, |snippets, _cx| {
25745                snippets.add_snippet_for_test(
25746                    None,
25747                    PathBuf::from("test_snippets.json"),
25748                    vec![
25749                        Arc::new(project::snippet_provider::Snippet {
25750                            prefix: vec![
25751                                "unlimited word count".to_string(),
25752                                "unlimit word count".to_string(),
25753                                "unlimited unknown".to_string(),
25754                            ],
25755                            body: "this is many words".to_string(),
25756                            description: Some("description".to_string()),
25757                            name: "multi-word snippet test".to_string(),
25758                        }),
25759                        Arc::new(project::snippet_provider::Snippet {
25760                            prefix: vec!["unsnip".to_string(), "@few".to_string()],
25761                            body: "fewer words".to_string(),
25762                            description: Some("alt description".to_string()),
25763                            name: "other name".to_string(),
25764                        }),
25765                        Arc::new(project::snippet_provider::Snippet {
25766                            prefix: vec!["ab aa".to_string()],
25767                            body: "abcd".to_string(),
25768                            description: None,
25769                            name: "alphabet".to_string(),
25770                        }),
25771                    ],
25772                );
25773            });
25774        })
25775    });
25776
25777    let get_completions = |cx: &mut EditorLspTestContext| {
25778        cx.update_editor(|editor, _, _| match &*editor.context_menu.borrow() {
25779            Some(CodeContextMenu::Completions(context_menu)) => {
25780                let entries = context_menu.entries.borrow();
25781                entries
25782                    .iter()
25783                    .map(|entry| entry.string.clone())
25784                    .collect_vec()
25785            }
25786            _ => vec![],
25787        })
25788    };
25789
25790    // snippets:
25791    //  @foo
25792    //  foo bar
25793    //
25794    // when typing:
25795    //
25796    // when typing:
25797    //  - if I type a symbol "open the completions with snippets only"
25798    //  - if I type a word character "open the completions menu" (if it had been open snippets only, clear it out)
25799    //
25800    // stuff we need:
25801    //  - filtering logic change?
25802    //  - remember how far back the completion started.
25803
25804    let test_cases: &[(&str, &[&str])] = &[
25805        (
25806            "un",
25807            &[
25808                "unsafe",
25809                "unlimit word count",
25810                "unlimited unknown",
25811                "unlimited word count",
25812                "unsnip",
25813            ],
25814        ),
25815        (
25816            "u ",
25817            &[
25818                "unlimit word count",
25819                "unlimited unknown",
25820                "unlimited word count",
25821            ],
25822        ),
25823        ("u a", &["ab aa", "unsafe"]), // unsAfe
25824        (
25825            "u u",
25826            &[
25827                "unsafe",
25828                "unlimit word count",
25829                "unlimited unknown", // ranked highest among snippets
25830                "unlimited word count",
25831                "unsnip",
25832            ],
25833        ),
25834        ("uw c", &["unlimit word count", "unlimited word count"]),
25835        (
25836            "u w",
25837            &[
25838                "unlimit word count",
25839                "unlimited word count",
25840                "unlimited unknown",
25841            ],
25842        ),
25843        ("u w ", &["unlimit word count", "unlimited word count"]),
25844        (
25845            "u ",
25846            &[
25847                "unlimit word count",
25848                "unlimited unknown",
25849                "unlimited word count",
25850            ],
25851        ),
25852        ("wor", &[]),
25853        ("uf", &["unsafe"]),
25854        ("af", &["unsafe"]),
25855        ("afu", &[]),
25856        (
25857            "ue",
25858            &["unsafe", "unlimited unknown", "unlimited word count"],
25859        ),
25860        ("@", &["@few"]),
25861        ("@few", &["@few"]),
25862        ("@ ", &[]),
25863        ("a@", &["@few"]),
25864        ("a@f", &["@few", "unsafe"]),
25865        ("a@fw", &["@few"]),
25866        ("a", &["ab aa", "unsafe"]),
25867        ("aa", &["ab aa"]),
25868        ("aaa", &["ab aa"]),
25869        ("ab", &["ab aa"]),
25870        ("ab ", &["ab aa"]),
25871        ("ab a", &["ab aa", "unsafe"]),
25872        ("ab ab", &["ab aa"]),
25873        ("ab ab aa", &["ab aa"]),
25874    ];
25875
25876    for &(input_to_simulate, expected_completions) in test_cases {
25877        cx.set_state("fn a() { ˇ }\n");
25878        for c in input_to_simulate.split("") {
25879            cx.simulate_input(c);
25880            cx.run_until_parked();
25881        }
25882        let expected_completions = expected_completions
25883            .iter()
25884            .map(|s| s.to_string())
25885            .collect_vec();
25886        assert_eq!(
25887            get_completions(&mut cx),
25888            expected_completions,
25889            "< actual / expected >, input = {input_to_simulate:?}",
25890        );
25891    }
25892}
25893
25894/// Handle completion request passing a marked string specifying where the completion
25895/// should be triggered from using '|' character, what range should be replaced, and what completions
25896/// should be returned using '<' and '>' to delimit the range.
25897///
25898/// Also see `handle_completion_request_with_insert_and_replace`.
25899#[track_caller]
25900pub fn handle_completion_request(
25901    marked_string: &str,
25902    completions: Vec<&'static str>,
25903    is_incomplete: bool,
25904    counter: Arc<AtomicUsize>,
25905    cx: &mut EditorLspTestContext,
25906) -> impl Future<Output = ()> {
25907    let complete_from_marker: TextRangeMarker = '|'.into();
25908    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25909    let (_, mut marked_ranges) = marked_text_ranges_by(
25910        marked_string,
25911        vec![complete_from_marker.clone(), replace_range_marker.clone()],
25912    );
25913
25914    let complete_from_position =
25915        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25916    let replace_range =
25917        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25918
25919    let mut request =
25920        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25921            let completions = completions.clone();
25922            counter.fetch_add(1, atomic::Ordering::Release);
25923            async move {
25924                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25925                assert_eq!(
25926                    params.text_document_position.position,
25927                    complete_from_position
25928                );
25929                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
25930                    is_incomplete,
25931                    item_defaults: None,
25932                    items: completions
25933                        .iter()
25934                        .map(|completion_text| lsp::CompletionItem {
25935                            label: completion_text.to_string(),
25936                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
25937                                range: replace_range,
25938                                new_text: completion_text.to_string(),
25939                            })),
25940                            ..Default::default()
25941                        })
25942                        .collect(),
25943                })))
25944            }
25945        });
25946
25947    async move {
25948        request.next().await;
25949    }
25950}
25951
25952/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
25953/// given instead, which also contains an `insert` range.
25954///
25955/// This function uses markers to define ranges:
25956/// - `|` marks the cursor position
25957/// - `<>` marks the replace range
25958/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
25959pub fn handle_completion_request_with_insert_and_replace(
25960    cx: &mut EditorLspTestContext,
25961    marked_string: &str,
25962    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
25963    counter: Arc<AtomicUsize>,
25964) -> impl Future<Output = ()> {
25965    let complete_from_marker: TextRangeMarker = '|'.into();
25966    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25967    let insert_range_marker: TextRangeMarker = ('{', '}').into();
25968
25969    let (_, mut marked_ranges) = marked_text_ranges_by(
25970        marked_string,
25971        vec![
25972            complete_from_marker.clone(),
25973            replace_range_marker.clone(),
25974            insert_range_marker.clone(),
25975        ],
25976    );
25977
25978    let complete_from_position =
25979        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25980    let replace_range =
25981        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25982
25983    let insert_range = match marked_ranges.remove(&insert_range_marker) {
25984        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
25985        _ => lsp::Range {
25986            start: replace_range.start,
25987            end: complete_from_position,
25988        },
25989    };
25990
25991    let mut request =
25992        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25993            let completions = completions.clone();
25994            counter.fetch_add(1, atomic::Ordering::Release);
25995            async move {
25996                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25997                assert_eq!(
25998                    params.text_document_position.position, complete_from_position,
25999                    "marker `|` position doesn't match",
26000                );
26001                Ok(Some(lsp::CompletionResponse::Array(
26002                    completions
26003                        .iter()
26004                        .map(|(label, new_text)| lsp::CompletionItem {
26005                            label: label.to_string(),
26006                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
26007                                lsp::InsertReplaceEdit {
26008                                    insert: insert_range,
26009                                    replace: replace_range,
26010                                    new_text: new_text.to_string(),
26011                                },
26012                            )),
26013                            ..Default::default()
26014                        })
26015                        .collect(),
26016                )))
26017            }
26018        });
26019
26020    async move {
26021        request.next().await;
26022    }
26023}
26024
26025fn handle_resolve_completion_request(
26026    cx: &mut EditorLspTestContext,
26027    edits: Option<Vec<(&'static str, &'static str)>>,
26028) -> impl Future<Output = ()> {
26029    let edits = edits.map(|edits| {
26030        edits
26031            .iter()
26032            .map(|(marked_string, new_text)| {
26033                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
26034                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
26035                lsp::TextEdit::new(replace_range, new_text.to_string())
26036            })
26037            .collect::<Vec<_>>()
26038    });
26039
26040    let mut request =
26041        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
26042            let edits = edits.clone();
26043            async move {
26044                Ok(lsp::CompletionItem {
26045                    additional_text_edits: edits,
26046                    ..Default::default()
26047                })
26048            }
26049        });
26050
26051    async move {
26052        request.next().await;
26053    }
26054}
26055
26056pub(crate) fn update_test_language_settings(
26057    cx: &mut TestAppContext,
26058    f: impl Fn(&mut AllLanguageSettingsContent),
26059) {
26060    cx.update(|cx| {
26061        SettingsStore::update_global(cx, |store, cx| {
26062            store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
26063        });
26064    });
26065}
26066
26067pub(crate) fn update_test_project_settings(
26068    cx: &mut TestAppContext,
26069    f: impl Fn(&mut ProjectSettingsContent),
26070) {
26071    cx.update(|cx| {
26072        SettingsStore::update_global(cx, |store, cx| {
26073            store.update_user_settings(cx, |settings| f(&mut settings.project));
26074        });
26075    });
26076}
26077
26078pub(crate) fn update_test_editor_settings(
26079    cx: &mut TestAppContext,
26080    f: impl Fn(&mut EditorSettingsContent),
26081) {
26082    cx.update(|cx| {
26083        SettingsStore::update_global(cx, |store, cx| {
26084            store.update_user_settings(cx, |settings| f(&mut settings.editor));
26085        })
26086    })
26087}
26088
26089pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
26090    cx.update(|cx| {
26091        assets::Assets.load_test_fonts(cx);
26092        let store = SettingsStore::test(cx);
26093        cx.set_global(store);
26094        theme::init(theme::LoadThemes::JustBase, cx);
26095        release_channel::init(SemanticVersion::default(), cx);
26096        crate::init(cx);
26097    });
26098    zlog::init_test();
26099    update_test_language_settings(cx, f);
26100}
26101
26102#[track_caller]
26103fn assert_hunk_revert(
26104    not_reverted_text_with_selections: &str,
26105    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
26106    expected_reverted_text_with_selections: &str,
26107    base_text: &str,
26108    cx: &mut EditorLspTestContext,
26109) {
26110    cx.set_state(not_reverted_text_with_selections);
26111    cx.set_head_text(base_text);
26112    cx.executor().run_until_parked();
26113
26114    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
26115        let snapshot = editor.snapshot(window, cx);
26116        let reverted_hunk_statuses = snapshot
26117            .buffer_snapshot()
26118            .diff_hunks_in_range(0..snapshot.buffer_snapshot().len())
26119            .map(|hunk| hunk.status().kind)
26120            .collect::<Vec<_>>();
26121
26122        editor.git_restore(&Default::default(), window, cx);
26123        reverted_hunk_statuses
26124    });
26125    cx.executor().run_until_parked();
26126    cx.assert_editor_state(expected_reverted_text_with_selections);
26127    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
26128}
26129
26130#[gpui::test(iterations = 10)]
26131async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
26132    init_test(cx, |_| {});
26133
26134    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
26135    let counter = diagnostic_requests.clone();
26136
26137    let fs = FakeFs::new(cx.executor());
26138    fs.insert_tree(
26139        path!("/a"),
26140        json!({
26141            "first.rs": "fn main() { let a = 5; }",
26142            "second.rs": "// Test file",
26143        }),
26144    )
26145    .await;
26146
26147    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
26148    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
26149    let cx = &mut VisualTestContext::from_window(*workspace, cx);
26150
26151    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26152    language_registry.add(rust_lang());
26153    let mut fake_servers = language_registry.register_fake_lsp(
26154        "Rust",
26155        FakeLspAdapter {
26156            capabilities: lsp::ServerCapabilities {
26157                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
26158                    lsp::DiagnosticOptions {
26159                        identifier: None,
26160                        inter_file_dependencies: true,
26161                        workspace_diagnostics: true,
26162                        work_done_progress_options: Default::default(),
26163                    },
26164                )),
26165                ..Default::default()
26166            },
26167            ..Default::default()
26168        },
26169    );
26170
26171    let editor = workspace
26172        .update(cx, |workspace, window, cx| {
26173            workspace.open_abs_path(
26174                PathBuf::from(path!("/a/first.rs")),
26175                OpenOptions::default(),
26176                window,
26177                cx,
26178            )
26179        })
26180        .unwrap()
26181        .await
26182        .unwrap()
26183        .downcast::<Editor>()
26184        .unwrap();
26185    let fake_server = fake_servers.next().await.unwrap();
26186    let server_id = fake_server.server.server_id();
26187    let mut first_request = fake_server
26188        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
26189            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
26190            let result_id = Some(new_result_id.to_string());
26191            assert_eq!(
26192                params.text_document.uri,
26193                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
26194            );
26195            async move {
26196                Ok(lsp::DocumentDiagnosticReportResult::Report(
26197                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
26198                        related_documents: None,
26199                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
26200                            items: Vec::new(),
26201                            result_id,
26202                        },
26203                    }),
26204                ))
26205            }
26206        });
26207
26208    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
26209        project.update(cx, |project, cx| {
26210            let buffer_id = editor
26211                .read(cx)
26212                .buffer()
26213                .read(cx)
26214                .as_singleton()
26215                .expect("created a singleton buffer")
26216                .read(cx)
26217                .remote_id();
26218            let buffer_result_id = project
26219                .lsp_store()
26220                .read(cx)
26221                .result_id(server_id, buffer_id, cx);
26222            assert_eq!(expected, buffer_result_id);
26223        });
26224    };
26225
26226    ensure_result_id(None, cx);
26227    cx.executor().advance_clock(Duration::from_millis(60));
26228    cx.executor().run_until_parked();
26229    assert_eq!(
26230        diagnostic_requests.load(atomic::Ordering::Acquire),
26231        1,
26232        "Opening file should trigger diagnostic request"
26233    );
26234    first_request
26235        .next()
26236        .await
26237        .expect("should have sent the first diagnostics pull request");
26238    ensure_result_id(Some("1".to_string()), cx);
26239
26240    // Editing should trigger diagnostics
26241    editor.update_in(cx, |editor, window, cx| {
26242        editor.handle_input("2", window, cx)
26243    });
26244    cx.executor().advance_clock(Duration::from_millis(60));
26245    cx.executor().run_until_parked();
26246    assert_eq!(
26247        diagnostic_requests.load(atomic::Ordering::Acquire),
26248        2,
26249        "Editing should trigger diagnostic request"
26250    );
26251    ensure_result_id(Some("2".to_string()), cx);
26252
26253    // Moving cursor should not trigger diagnostic request
26254    editor.update_in(cx, |editor, window, cx| {
26255        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26256            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
26257        });
26258    });
26259    cx.executor().advance_clock(Duration::from_millis(60));
26260    cx.executor().run_until_parked();
26261    assert_eq!(
26262        diagnostic_requests.load(atomic::Ordering::Acquire),
26263        2,
26264        "Cursor movement should not trigger diagnostic request"
26265    );
26266    ensure_result_id(Some("2".to_string()), cx);
26267    // Multiple rapid edits should be debounced
26268    for _ in 0..5 {
26269        editor.update_in(cx, |editor, window, cx| {
26270            editor.handle_input("x", window, cx)
26271        });
26272    }
26273    cx.executor().advance_clock(Duration::from_millis(60));
26274    cx.executor().run_until_parked();
26275
26276    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
26277    assert!(
26278        final_requests <= 4,
26279        "Multiple rapid edits should be debounced (got {final_requests} requests)",
26280    );
26281    ensure_result_id(Some(final_requests.to_string()), cx);
26282}
26283
26284#[gpui::test]
26285async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
26286    // Regression test for issue #11671
26287    // Previously, adding a cursor after moving multiple cursors would reset
26288    // the cursor count instead of adding to the existing cursors.
26289    init_test(cx, |_| {});
26290    let mut cx = EditorTestContext::new(cx).await;
26291
26292    // Create a simple buffer with cursor at start
26293    cx.set_state(indoc! {"
26294        ˇaaaa
26295        bbbb
26296        cccc
26297        dddd
26298        eeee
26299        ffff
26300        gggg
26301        hhhh"});
26302
26303    // Add 2 cursors below (so we have 3 total)
26304    cx.update_editor(|editor, window, cx| {
26305        editor.add_selection_below(&Default::default(), window, cx);
26306        editor.add_selection_below(&Default::default(), window, cx);
26307    });
26308
26309    // Verify we have 3 cursors
26310    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
26311    assert_eq!(
26312        initial_count, 3,
26313        "Should have 3 cursors after adding 2 below"
26314    );
26315
26316    // Move down one line
26317    cx.update_editor(|editor, window, cx| {
26318        editor.move_down(&MoveDown, window, cx);
26319    });
26320
26321    // Add another cursor below
26322    cx.update_editor(|editor, window, cx| {
26323        editor.add_selection_below(&Default::default(), window, cx);
26324    });
26325
26326    // Should now have 4 cursors (3 original + 1 new)
26327    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
26328    assert_eq!(
26329        final_count, 4,
26330        "Should have 4 cursors after moving and adding another"
26331    );
26332}
26333
26334#[gpui::test]
26335async fn test_add_selection_skip_soft_wrap_option(cx: &mut TestAppContext) {
26336    init_test(cx, |_| {});
26337
26338    let mut cx = EditorTestContext::new(cx).await;
26339
26340    cx.set_state(indoc!(
26341        r#"ˇThis is a very long line that will be wrapped when soft wrapping is enabled
26342           Second line here"#
26343    ));
26344
26345    cx.update_editor(|editor, window, cx| {
26346        // Enable soft wrapping with a narrow width to force soft wrapping and
26347        // confirm that more than 2 rows are being displayed.
26348        editor.set_wrap_width(Some(100.0.into()), cx);
26349        assert!(editor.display_text(cx).lines().count() > 2);
26350
26351        editor.add_selection_below(
26352            &AddSelectionBelow {
26353                skip_soft_wrap: true,
26354            },
26355            window,
26356            cx,
26357        );
26358
26359        assert_eq!(
26360            display_ranges(editor, cx),
26361            &[
26362                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
26363                DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(8), 0),
26364            ]
26365        );
26366
26367        editor.add_selection_above(
26368            &AddSelectionAbove {
26369                skip_soft_wrap: true,
26370            },
26371            window,
26372            cx,
26373        );
26374
26375        assert_eq!(
26376            display_ranges(editor, cx),
26377            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
26378        );
26379
26380        editor.add_selection_below(
26381            &AddSelectionBelow {
26382                skip_soft_wrap: false,
26383            },
26384            window,
26385            cx,
26386        );
26387
26388        assert_eq!(
26389            display_ranges(editor, cx),
26390            &[
26391                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
26392                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
26393            ]
26394        );
26395
26396        editor.add_selection_above(
26397            &AddSelectionAbove {
26398                skip_soft_wrap: false,
26399            },
26400            window,
26401            cx,
26402        );
26403
26404        assert_eq!(
26405            display_ranges(editor, cx),
26406            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
26407        );
26408    });
26409}
26410
26411#[gpui::test(iterations = 10)]
26412async fn test_document_colors(cx: &mut TestAppContext) {
26413    let expected_color = Rgba {
26414        r: 0.33,
26415        g: 0.33,
26416        b: 0.33,
26417        a: 0.33,
26418    };
26419
26420    init_test(cx, |_| {});
26421
26422    let fs = FakeFs::new(cx.executor());
26423    fs.insert_tree(
26424        path!("/a"),
26425        json!({
26426            "first.rs": "fn main() { let a = 5; }",
26427        }),
26428    )
26429    .await;
26430
26431    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
26432    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
26433    let cx = &mut VisualTestContext::from_window(*workspace, cx);
26434
26435    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26436    language_registry.add(rust_lang());
26437    let mut fake_servers = language_registry.register_fake_lsp(
26438        "Rust",
26439        FakeLspAdapter {
26440            capabilities: lsp::ServerCapabilities {
26441                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
26442                ..lsp::ServerCapabilities::default()
26443            },
26444            name: "rust-analyzer",
26445            ..FakeLspAdapter::default()
26446        },
26447    );
26448    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
26449        "Rust",
26450        FakeLspAdapter {
26451            capabilities: lsp::ServerCapabilities {
26452                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
26453                ..lsp::ServerCapabilities::default()
26454            },
26455            name: "not-rust-analyzer",
26456            ..FakeLspAdapter::default()
26457        },
26458    );
26459
26460    let editor = workspace
26461        .update(cx, |workspace, window, cx| {
26462            workspace.open_abs_path(
26463                PathBuf::from(path!("/a/first.rs")),
26464                OpenOptions::default(),
26465                window,
26466                cx,
26467            )
26468        })
26469        .unwrap()
26470        .await
26471        .unwrap()
26472        .downcast::<Editor>()
26473        .unwrap();
26474    let fake_language_server = fake_servers.next().await.unwrap();
26475    let fake_language_server_without_capabilities =
26476        fake_servers_without_capabilities.next().await.unwrap();
26477    let requests_made = Arc::new(AtomicUsize::new(0));
26478    let closure_requests_made = Arc::clone(&requests_made);
26479    let mut color_request_handle = fake_language_server
26480        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
26481            let requests_made = Arc::clone(&closure_requests_made);
26482            async move {
26483                assert_eq!(
26484                    params.text_document.uri,
26485                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
26486                );
26487                requests_made.fetch_add(1, atomic::Ordering::Release);
26488                Ok(vec![
26489                    lsp::ColorInformation {
26490                        range: lsp::Range {
26491                            start: lsp::Position {
26492                                line: 0,
26493                                character: 0,
26494                            },
26495                            end: lsp::Position {
26496                                line: 0,
26497                                character: 1,
26498                            },
26499                        },
26500                        color: lsp::Color {
26501                            red: 0.33,
26502                            green: 0.33,
26503                            blue: 0.33,
26504                            alpha: 0.33,
26505                        },
26506                    },
26507                    lsp::ColorInformation {
26508                        range: lsp::Range {
26509                            start: lsp::Position {
26510                                line: 0,
26511                                character: 0,
26512                            },
26513                            end: lsp::Position {
26514                                line: 0,
26515                                character: 1,
26516                            },
26517                        },
26518                        color: lsp::Color {
26519                            red: 0.33,
26520                            green: 0.33,
26521                            blue: 0.33,
26522                            alpha: 0.33,
26523                        },
26524                    },
26525                ])
26526            }
26527        });
26528
26529    let _handle = fake_language_server_without_capabilities
26530        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
26531            panic!("Should not be called");
26532        });
26533    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26534    color_request_handle.next().await.unwrap();
26535    cx.run_until_parked();
26536    assert_eq!(
26537        1,
26538        requests_made.load(atomic::Ordering::Acquire),
26539        "Should query for colors once per editor open"
26540    );
26541    editor.update_in(cx, |editor, _, cx| {
26542        assert_eq!(
26543            vec![expected_color],
26544            extract_color_inlays(editor, cx),
26545            "Should have an initial inlay"
26546        );
26547    });
26548
26549    // opening another file in a split should not influence the LSP query counter
26550    workspace
26551        .update(cx, |workspace, window, cx| {
26552            assert_eq!(
26553                workspace.panes().len(),
26554                1,
26555                "Should have one pane with one editor"
26556            );
26557            workspace.move_item_to_pane_in_direction(
26558                &MoveItemToPaneInDirection {
26559                    direction: SplitDirection::Right,
26560                    focus: false,
26561                    clone: true,
26562                },
26563                window,
26564                cx,
26565            );
26566        })
26567        .unwrap();
26568    cx.run_until_parked();
26569    workspace
26570        .update(cx, |workspace, _, cx| {
26571            let panes = workspace.panes();
26572            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
26573            for pane in panes {
26574                let editor = pane
26575                    .read(cx)
26576                    .active_item()
26577                    .and_then(|item| item.downcast::<Editor>())
26578                    .expect("Should have opened an editor in each split");
26579                let editor_file = editor
26580                    .read(cx)
26581                    .buffer()
26582                    .read(cx)
26583                    .as_singleton()
26584                    .expect("test deals with singleton buffers")
26585                    .read(cx)
26586                    .file()
26587                    .expect("test buffese should have a file")
26588                    .path();
26589                assert_eq!(
26590                    editor_file.as_ref(),
26591                    rel_path("first.rs"),
26592                    "Both editors should be opened for the same file"
26593                )
26594            }
26595        })
26596        .unwrap();
26597
26598    cx.executor().advance_clock(Duration::from_millis(500));
26599    let save = editor.update_in(cx, |editor, window, cx| {
26600        editor.move_to_end(&MoveToEnd, window, cx);
26601        editor.handle_input("dirty", window, cx);
26602        editor.save(
26603            SaveOptions {
26604                format: true,
26605                autosave: true,
26606            },
26607            project.clone(),
26608            window,
26609            cx,
26610        )
26611    });
26612    save.await.unwrap();
26613
26614    color_request_handle.next().await.unwrap();
26615    cx.run_until_parked();
26616    assert_eq!(
26617        2,
26618        requests_made.load(atomic::Ordering::Acquire),
26619        "Should query for colors once per save (deduplicated) and once per formatting after save"
26620    );
26621
26622    drop(editor);
26623    let close = workspace
26624        .update(cx, |workspace, window, cx| {
26625            workspace.active_pane().update(cx, |pane, cx| {
26626                pane.close_active_item(&CloseActiveItem::default(), window, cx)
26627            })
26628        })
26629        .unwrap();
26630    close.await.unwrap();
26631    let close = workspace
26632        .update(cx, |workspace, window, cx| {
26633            workspace.active_pane().update(cx, |pane, cx| {
26634                pane.close_active_item(&CloseActiveItem::default(), window, cx)
26635            })
26636        })
26637        .unwrap();
26638    close.await.unwrap();
26639    assert_eq!(
26640        2,
26641        requests_made.load(atomic::Ordering::Acquire),
26642        "After saving and closing all editors, no extra requests should be made"
26643    );
26644    workspace
26645        .update(cx, |workspace, _, cx| {
26646            assert!(
26647                workspace.active_item(cx).is_none(),
26648                "Should close all editors"
26649            )
26650        })
26651        .unwrap();
26652
26653    workspace
26654        .update(cx, |workspace, window, cx| {
26655            workspace.active_pane().update(cx, |pane, cx| {
26656                pane.navigate_backward(&workspace::GoBack, window, cx);
26657            })
26658        })
26659        .unwrap();
26660    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26661    cx.run_until_parked();
26662    let editor = workspace
26663        .update(cx, |workspace, _, cx| {
26664            workspace
26665                .active_item(cx)
26666                .expect("Should have reopened the editor again after navigating back")
26667                .downcast::<Editor>()
26668                .expect("Should be an editor")
26669        })
26670        .unwrap();
26671
26672    assert_eq!(
26673        2,
26674        requests_made.load(atomic::Ordering::Acquire),
26675        "Cache should be reused on buffer close and reopen"
26676    );
26677    editor.update(cx, |editor, cx| {
26678        assert_eq!(
26679            vec![expected_color],
26680            extract_color_inlays(editor, cx),
26681            "Should have an initial inlay"
26682        );
26683    });
26684
26685    drop(color_request_handle);
26686    let closure_requests_made = Arc::clone(&requests_made);
26687    let mut empty_color_request_handle = fake_language_server
26688        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
26689            let requests_made = Arc::clone(&closure_requests_made);
26690            async move {
26691                assert_eq!(
26692                    params.text_document.uri,
26693                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
26694                );
26695                requests_made.fetch_add(1, atomic::Ordering::Release);
26696                Ok(Vec::new())
26697            }
26698        });
26699    let save = editor.update_in(cx, |editor, window, cx| {
26700        editor.move_to_end(&MoveToEnd, window, cx);
26701        editor.handle_input("dirty_again", window, cx);
26702        editor.save(
26703            SaveOptions {
26704                format: false,
26705                autosave: true,
26706            },
26707            project.clone(),
26708            window,
26709            cx,
26710        )
26711    });
26712    save.await.unwrap();
26713
26714    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26715    empty_color_request_handle.next().await.unwrap();
26716    cx.run_until_parked();
26717    assert_eq!(
26718        3,
26719        requests_made.load(atomic::Ordering::Acquire),
26720        "Should query for colors once per save only, as formatting was not requested"
26721    );
26722    editor.update(cx, |editor, cx| {
26723        assert_eq!(
26724            Vec::<Rgba>::new(),
26725            extract_color_inlays(editor, cx),
26726            "Should clear all colors when the server returns an empty response"
26727        );
26728    });
26729}
26730
26731#[gpui::test]
26732async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
26733    init_test(cx, |_| {});
26734    let (editor, cx) = cx.add_window_view(Editor::single_line);
26735    editor.update_in(cx, |editor, window, cx| {
26736        editor.set_text("oops\n\nwow\n", window, cx)
26737    });
26738    cx.run_until_parked();
26739    editor.update(cx, |editor, cx| {
26740        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
26741    });
26742    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
26743    cx.run_until_parked();
26744    editor.update(cx, |editor, cx| {
26745        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
26746    });
26747}
26748
26749#[gpui::test]
26750async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
26751    init_test(cx, |_| {});
26752
26753    cx.update(|cx| {
26754        register_project_item::<Editor>(cx);
26755    });
26756
26757    let fs = FakeFs::new(cx.executor());
26758    fs.insert_tree("/root1", json!({})).await;
26759    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
26760        .await;
26761
26762    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
26763    let (workspace, cx) =
26764        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
26765
26766    let worktree_id = project.update(cx, |project, cx| {
26767        project.worktrees(cx).next().unwrap().read(cx).id()
26768    });
26769
26770    let handle = workspace
26771        .update_in(cx, |workspace, window, cx| {
26772            let project_path = (worktree_id, rel_path("one.pdf"));
26773            workspace.open_path(project_path, None, true, window, cx)
26774        })
26775        .await
26776        .unwrap();
26777
26778    assert_eq!(
26779        handle.to_any().entity_type(),
26780        TypeId::of::<InvalidItemView>()
26781    );
26782}
26783
26784#[gpui::test]
26785async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
26786    init_test(cx, |_| {});
26787
26788    let language = Arc::new(Language::new(
26789        LanguageConfig::default(),
26790        Some(tree_sitter_rust::LANGUAGE.into()),
26791    ));
26792
26793    // Test hierarchical sibling navigation
26794    let text = r#"
26795        fn outer() {
26796            if condition {
26797                let a = 1;
26798            }
26799            let b = 2;
26800        }
26801
26802        fn another() {
26803            let c = 3;
26804        }
26805    "#;
26806
26807    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
26808    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
26809    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
26810
26811    // Wait for parsing to complete
26812    editor
26813        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
26814        .await;
26815
26816    editor.update_in(cx, |editor, window, cx| {
26817        // Start by selecting "let a = 1;" inside the if block
26818        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26819            s.select_display_ranges([
26820                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
26821            ]);
26822        });
26823
26824        let initial_selection = editor
26825            .selections
26826            .display_ranges(&editor.display_snapshot(cx));
26827        assert_eq!(initial_selection.len(), 1, "Should have one selection");
26828
26829        // Test select next sibling - should move up levels to find the next sibling
26830        // Since "let a = 1;" has no siblings in the if block, it should move up
26831        // to find "let b = 2;" which is a sibling of the if block
26832        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
26833        let next_selection = editor
26834            .selections
26835            .display_ranges(&editor.display_snapshot(cx));
26836
26837        // Should have a selection and it should be different from the initial
26838        assert_eq!(
26839            next_selection.len(),
26840            1,
26841            "Should have one selection after next"
26842        );
26843        assert_ne!(
26844            next_selection[0], initial_selection[0],
26845            "Next sibling selection should be different"
26846        );
26847
26848        // Test hierarchical navigation by going to the end of the current function
26849        // and trying to navigate to the next function
26850        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26851            s.select_display_ranges([
26852                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
26853            ]);
26854        });
26855
26856        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
26857        let function_next_selection = editor
26858            .selections
26859            .display_ranges(&editor.display_snapshot(cx));
26860
26861        // Should move to the next function
26862        assert_eq!(
26863            function_next_selection.len(),
26864            1,
26865            "Should have one selection after function next"
26866        );
26867
26868        // Test select previous sibling navigation
26869        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
26870        let prev_selection = editor
26871            .selections
26872            .display_ranges(&editor.display_snapshot(cx));
26873
26874        // Should have a selection and it should be different
26875        assert_eq!(
26876            prev_selection.len(),
26877            1,
26878            "Should have one selection after prev"
26879        );
26880        assert_ne!(
26881            prev_selection[0], function_next_selection[0],
26882            "Previous sibling selection should be different from next"
26883        );
26884    });
26885}
26886
26887#[gpui::test]
26888async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
26889    init_test(cx, |_| {});
26890
26891    let mut cx = EditorTestContext::new(cx).await;
26892    cx.set_state(
26893        "let ˇvariable = 42;
26894let another = variable + 1;
26895let result = variable * 2;",
26896    );
26897
26898    // Set up document highlights manually (simulating LSP response)
26899    cx.update_editor(|editor, _window, cx| {
26900        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
26901
26902        // Create highlights for "variable" occurrences
26903        let highlight_ranges = [
26904            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
26905            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
26906            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
26907        ];
26908
26909        let anchor_ranges: Vec<_> = highlight_ranges
26910            .iter()
26911            .map(|range| range.clone().to_anchors(&buffer_snapshot))
26912            .collect();
26913
26914        editor.highlight_background::<DocumentHighlightRead>(
26915            &anchor_ranges,
26916            |theme| theme.colors().editor_document_highlight_read_background,
26917            cx,
26918        );
26919    });
26920
26921    // Go to next highlight - should move to second "variable"
26922    cx.update_editor(|editor, window, cx| {
26923        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26924    });
26925    cx.assert_editor_state(
26926        "let variable = 42;
26927let another = ˇvariable + 1;
26928let result = variable * 2;",
26929    );
26930
26931    // Go to next highlight - should move to third "variable"
26932    cx.update_editor(|editor, window, cx| {
26933        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26934    });
26935    cx.assert_editor_state(
26936        "let variable = 42;
26937let another = variable + 1;
26938let result = ˇvariable * 2;",
26939    );
26940
26941    // Go to next highlight - should stay at third "variable" (no wrap-around)
26942    cx.update_editor(|editor, window, cx| {
26943        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
26944    });
26945    cx.assert_editor_state(
26946        "let variable = 42;
26947let another = variable + 1;
26948let result = ˇvariable * 2;",
26949    );
26950
26951    // Now test going backwards from third position
26952    cx.update_editor(|editor, window, cx| {
26953        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26954    });
26955    cx.assert_editor_state(
26956        "let variable = 42;
26957let another = ˇvariable + 1;
26958let result = variable * 2;",
26959    );
26960
26961    // Go to previous highlight - should move to first "variable"
26962    cx.update_editor(|editor, window, cx| {
26963        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26964    });
26965    cx.assert_editor_state(
26966        "let ˇvariable = 42;
26967let another = variable + 1;
26968let result = variable * 2;",
26969    );
26970
26971    // Go to previous highlight - should stay on first "variable"
26972    cx.update_editor(|editor, window, cx| {
26973        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
26974    });
26975    cx.assert_editor_state(
26976        "let ˇvariable = 42;
26977let another = variable + 1;
26978let result = variable * 2;",
26979    );
26980}
26981
26982#[gpui::test]
26983async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
26984    cx: &mut gpui::TestAppContext,
26985) {
26986    init_test(cx, |_| {});
26987
26988    let url = "https://zed.dev";
26989
26990    let markdown_language = Arc::new(Language::new(
26991        LanguageConfig {
26992            name: "Markdown".into(),
26993            ..LanguageConfig::default()
26994        },
26995        None,
26996    ));
26997
26998    let mut cx = EditorTestContext::new(cx).await;
26999    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
27000    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
27001
27002    cx.update_editor(|editor, window, cx| {
27003        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
27004        editor.paste(&Paste, window, cx);
27005    });
27006
27007    cx.assert_editor_state(&format!(
27008        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)"
27009    ));
27010}
27011
27012#[gpui::test]
27013async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
27014    cx: &mut gpui::TestAppContext,
27015) {
27016    init_test(cx, |_| {});
27017
27018    let url = "https://zed.dev";
27019
27020    let markdown_language = Arc::new(Language::new(
27021        LanguageConfig {
27022            name: "Markdown".into(),
27023            ..LanguageConfig::default()
27024        },
27025        None,
27026    ));
27027
27028    let mut cx = EditorTestContext::new(cx).await;
27029    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
27030    cx.set_state(&format!(
27031        "Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»"
27032    ));
27033
27034    cx.update_editor(|editor, window, cx| {
27035        editor.copy(&Copy, window, cx);
27036    });
27037
27038    cx.set_state(&format!(
27039        "Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}"
27040    ));
27041
27042    cx.update_editor(|editor, window, cx| {
27043        editor.paste(&Paste, window, cx);
27044    });
27045
27046    cx.assert_editor_state(&format!(
27047        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}"
27048    ));
27049}
27050
27051#[gpui::test]
27052async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link(
27053    cx: &mut gpui::TestAppContext,
27054) {
27055    init_test(cx, |_| {});
27056
27057    let url = "https://zed.dev";
27058
27059    let markdown_language = Arc::new(Language::new(
27060        LanguageConfig {
27061            name: "Markdown".into(),
27062            ..LanguageConfig::default()
27063        },
27064        None,
27065    ));
27066
27067    let mut cx = EditorTestContext::new(cx).await;
27068    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
27069    cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»");
27070
27071    cx.update_editor(|editor, window, cx| {
27072        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
27073        editor.paste(&Paste, window, cx);
27074    });
27075
27076    cx.assert_editor_state(&format!("Please visit zed's homepage: {url}ˇ"));
27077}
27078
27079#[gpui::test]
27080async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link(
27081    cx: &mut gpui::TestAppContext,
27082) {
27083    init_test(cx, |_| {});
27084
27085    let text = "Awesome";
27086
27087    let markdown_language = Arc::new(Language::new(
27088        LanguageConfig {
27089            name: "Markdown".into(),
27090            ..LanguageConfig::default()
27091        },
27092        None,
27093    ));
27094
27095    let mut cx = EditorTestContext::new(cx).await;
27096    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
27097    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»");
27098
27099    cx.update_editor(|editor, window, cx| {
27100        cx.write_to_clipboard(ClipboardItem::new_string(text.to_string()));
27101        editor.paste(&Paste, window, cx);
27102    });
27103
27104    cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ"));
27105}
27106
27107#[gpui::test]
27108async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language(
27109    cx: &mut gpui::TestAppContext,
27110) {
27111    init_test(cx, |_| {});
27112
27113    let url = "https://zed.dev";
27114
27115    let markdown_language = Arc::new(Language::new(
27116        LanguageConfig {
27117            name: "Rust".into(),
27118            ..LanguageConfig::default()
27119        },
27120        None,
27121    ));
27122
27123    let mut cx = EditorTestContext::new(cx).await;
27124    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
27125    cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
27126
27127    cx.update_editor(|editor, window, cx| {
27128        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
27129        editor.paste(&Paste, window, cx);
27130    });
27131
27132    cx.assert_editor_state(&format!(
27133        "// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)"
27134    ));
27135}
27136
27137#[gpui::test]
27138async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer(
27139    cx: &mut TestAppContext,
27140) {
27141    init_test(cx, |_| {});
27142
27143    let url = "https://zed.dev";
27144
27145    let markdown_language = Arc::new(Language::new(
27146        LanguageConfig {
27147            name: "Markdown".into(),
27148            ..LanguageConfig::default()
27149        },
27150        None,
27151    ));
27152
27153    let (editor, cx) = cx.add_window_view(|window, cx| {
27154        let multi_buffer = MultiBuffer::build_multi(
27155            [
27156                ("this will embed -> link", vec![Point::row_range(0..1)]),
27157                ("this will replace -> link", vec![Point::row_range(0..1)]),
27158            ],
27159            cx,
27160        );
27161        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
27162        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27163            s.select_ranges(vec![
27164                Point::new(0, 19)..Point::new(0, 23),
27165                Point::new(1, 21)..Point::new(1, 25),
27166            ])
27167        });
27168        let first_buffer_id = multi_buffer
27169            .read(cx)
27170            .excerpt_buffer_ids()
27171            .into_iter()
27172            .next()
27173            .unwrap();
27174        let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
27175        first_buffer.update(cx, |buffer, cx| {
27176            buffer.set_language(Some(markdown_language.clone()), cx);
27177        });
27178
27179        editor
27180    });
27181    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
27182
27183    cx.update_editor(|editor, window, cx| {
27184        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
27185        editor.paste(&Paste, window, cx);
27186    });
27187
27188    cx.assert_editor_state(&format!(
27189        "this will embed -> [link]({url}\nthis will replace -> {url}ˇ"
27190    ));
27191}
27192
27193#[gpui::test]
27194async fn test_race_in_multibuffer_save(cx: &mut TestAppContext) {
27195    init_test(cx, |_| {});
27196
27197    let fs = FakeFs::new(cx.executor());
27198    fs.insert_tree(
27199        path!("/project"),
27200        json!({
27201            "first.rs": "# First Document\nSome content here.",
27202            "second.rs": "Plain text content for second file.",
27203        }),
27204    )
27205    .await;
27206
27207    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
27208    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
27209    let cx = &mut VisualTestContext::from_window(*workspace, cx);
27210
27211    let language = rust_lang();
27212    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
27213    language_registry.add(language.clone());
27214    let mut fake_servers = language_registry.register_fake_lsp(
27215        "Rust",
27216        FakeLspAdapter {
27217            ..FakeLspAdapter::default()
27218        },
27219    );
27220
27221    let buffer1 = project
27222        .update(cx, |project, cx| {
27223            project.open_local_buffer(PathBuf::from(path!("/project/first.rs")), cx)
27224        })
27225        .await
27226        .unwrap();
27227    let buffer2 = project
27228        .update(cx, |project, cx| {
27229            project.open_local_buffer(PathBuf::from(path!("/project/second.rs")), cx)
27230        })
27231        .await
27232        .unwrap();
27233
27234    let multi_buffer = cx.new(|cx| {
27235        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
27236        multi_buffer.set_excerpts_for_path(
27237            PathKey::for_buffer(&buffer1, cx),
27238            buffer1.clone(),
27239            [Point::zero()..buffer1.read(cx).max_point()],
27240            3,
27241            cx,
27242        );
27243        multi_buffer.set_excerpts_for_path(
27244            PathKey::for_buffer(&buffer2, cx),
27245            buffer2.clone(),
27246            [Point::zero()..buffer1.read(cx).max_point()],
27247            3,
27248            cx,
27249        );
27250        multi_buffer
27251    });
27252
27253    let (editor, cx) = cx.add_window_view(|window, cx| {
27254        Editor::new(
27255            EditorMode::full(),
27256            multi_buffer,
27257            Some(project.clone()),
27258            window,
27259            cx,
27260        )
27261    });
27262
27263    let fake_language_server = fake_servers.next().await.unwrap();
27264
27265    buffer1.update(cx, |buffer, cx| buffer.edit([(0..0, "hello!")], None, cx));
27266
27267    let save = editor.update_in(cx, |editor, window, cx| {
27268        assert!(editor.is_dirty(cx));
27269
27270        editor.save(
27271            SaveOptions {
27272                format: true,
27273                autosave: true,
27274            },
27275            project,
27276            window,
27277            cx,
27278        )
27279    });
27280    let (start_edit_tx, start_edit_rx) = oneshot::channel();
27281    let (done_edit_tx, done_edit_rx) = oneshot::channel();
27282    let mut done_edit_rx = Some(done_edit_rx);
27283    let mut start_edit_tx = Some(start_edit_tx);
27284
27285    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| {
27286        start_edit_tx.take().unwrap().send(()).unwrap();
27287        let done_edit_rx = done_edit_rx.take().unwrap();
27288        async move {
27289            done_edit_rx.await.unwrap();
27290            Ok(None)
27291        }
27292    });
27293
27294    start_edit_rx.await.unwrap();
27295    buffer2
27296        .update(cx, |buffer, cx| buffer.edit([(0..0, "world!")], None, cx))
27297        .unwrap();
27298
27299    done_edit_tx.send(()).unwrap();
27300
27301    save.await.unwrap();
27302    cx.update(|_, cx| assert!(editor.is_dirty(cx)));
27303}
27304
27305#[track_caller]
27306fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
27307    editor
27308        .all_inlays(cx)
27309        .into_iter()
27310        .filter_map(|inlay| inlay.get_color())
27311        .map(Rgba::from)
27312        .collect()
27313}
27314
27315#[gpui::test]
27316fn test_duplicate_line_up_on_last_line_without_newline(cx: &mut TestAppContext) {
27317    init_test(cx, |_| {});
27318
27319    let editor = cx.add_window(|window, cx| {
27320        let buffer = MultiBuffer::build_simple("line1\nline2", cx);
27321        build_editor(buffer, window, cx)
27322    });
27323
27324    editor
27325        .update(cx, |editor, window, cx| {
27326            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27327                s.select_display_ranges([
27328                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
27329                ])
27330            });
27331
27332            editor.duplicate_line_up(&DuplicateLineUp, window, cx);
27333
27334            assert_eq!(
27335                editor.display_text(cx),
27336                "line1\nline2\nline2",
27337                "Duplicating last line upward should create duplicate above, not on same line"
27338            );
27339
27340            assert_eq!(
27341                editor
27342                    .selections
27343                    .display_ranges(&editor.display_snapshot(cx)),
27344                vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)],
27345                "Selection should move to the duplicated line"
27346            );
27347        })
27348        .unwrap();
27349}
27350
27351#[gpui::test]
27352async fn test_copy_line_without_trailing_newline(cx: &mut TestAppContext) {
27353    init_test(cx, |_| {});
27354
27355    let mut cx = EditorTestContext::new(cx).await;
27356
27357    cx.set_state("line1\nline2ˇ");
27358
27359    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
27360
27361    let clipboard_text = cx
27362        .read_from_clipboard()
27363        .and_then(|item| item.text().as_deref().map(str::to_string));
27364
27365    assert_eq!(
27366        clipboard_text,
27367        Some("line2\n".to_string()),
27368        "Copying a line without trailing newline should include a newline"
27369    );
27370
27371    cx.set_state("line1\nˇ");
27372
27373    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
27374
27375    cx.assert_editor_state("line1\nline2\nˇ");
27376}
27377
27378#[gpui::test]
27379async fn test_multi_selection_copy_with_newline_between_copied_lines(cx: &mut TestAppContext) {
27380    init_test(cx, |_| {});
27381
27382    let mut cx = EditorTestContext::new(cx).await;
27383
27384    cx.set_state("ˇline1\nˇline2\nˇline3\n");
27385
27386    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
27387
27388    let clipboard_text = cx
27389        .read_from_clipboard()
27390        .and_then(|item| item.text().as_deref().map(str::to_string));
27391
27392    assert_eq!(
27393        clipboard_text,
27394        Some("line1\nline2\nline3\n".to_string()),
27395        "Copying multiple lines should include a single newline between lines"
27396    );
27397
27398    cx.set_state("lineA\nˇ");
27399
27400    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
27401
27402    cx.assert_editor_state("lineA\nline1\nline2\nline3\nˇ");
27403}
27404
27405#[gpui::test]
27406async fn test_multi_selection_cut_with_newline_between_copied_lines(cx: &mut TestAppContext) {
27407    init_test(cx, |_| {});
27408
27409    let mut cx = EditorTestContext::new(cx).await;
27410
27411    cx.set_state("ˇline1\nˇline2\nˇline3\n");
27412
27413    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
27414
27415    let clipboard_text = cx
27416        .read_from_clipboard()
27417        .and_then(|item| item.text().as_deref().map(str::to_string));
27418
27419    assert_eq!(
27420        clipboard_text,
27421        Some("line1\nline2\nline3\n".to_string()),
27422        "Copying multiple lines should include a single newline between lines"
27423    );
27424
27425    cx.set_state("lineA\nˇ");
27426
27427    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
27428
27429    cx.assert_editor_state("lineA\nline1\nline2\nline3\nˇ");
27430}
27431
27432#[gpui::test]
27433async fn test_end_of_editor_context(cx: &mut TestAppContext) {
27434    init_test(cx, |_| {});
27435
27436    let mut cx = EditorTestContext::new(cx).await;
27437
27438    cx.set_state("line1\nline2ˇ");
27439    cx.update_editor(|e, window, cx| {
27440        e.set_mode(EditorMode::SingleLine);
27441        assert!(e.key_context(window, cx).contains("end_of_input"));
27442    });
27443    cx.set_state("ˇline1\nline2");
27444    cx.update_editor(|e, window, cx| {
27445        assert!(!e.key_context(window, cx).contains("end_of_input"));
27446    });
27447    cx.set_state("line1ˇ\nline2");
27448    cx.update_editor(|e, window, cx| {
27449        assert!(!e.key_context(window, cx).contains("end_of_input"));
27450    });
27451}
27452
27453#[gpui::test]
27454async fn test_sticky_scroll(cx: &mut TestAppContext) {
27455    init_test(cx, |_| {});
27456    let mut cx = EditorTestContext::new(cx).await;
27457
27458    let buffer = indoc! {"
27459            ˇfn foo() {
27460                let abc = 123;
27461            }
27462            struct Bar;
27463            impl Bar {
27464                fn new() -> Self {
27465                    Self
27466                }
27467            }
27468            fn baz() {
27469            }
27470        "};
27471    cx.set_state(&buffer);
27472
27473    cx.update_editor(|e, _, cx| {
27474        e.buffer()
27475            .read(cx)
27476            .as_singleton()
27477            .unwrap()
27478            .update(cx, |buffer, cx| {
27479                buffer.set_language(Some(rust_lang()), cx);
27480            })
27481    });
27482
27483    let mut sticky_headers = |offset: ScrollOffset| {
27484        cx.update_editor(|e, window, cx| {
27485            e.scroll(gpui::Point { x: 0., y: offset }, None, window, cx);
27486            EditorElement::sticky_headers(&e, &e.snapshot(window, cx), cx)
27487                .into_iter()
27488                .map(
27489                    |StickyHeader {
27490                         start_point,
27491                         offset,
27492                         ..
27493                     }| { (start_point, offset) },
27494                )
27495                .collect::<Vec<_>>()
27496        })
27497    };
27498
27499    let fn_foo = Point { row: 0, column: 0 };
27500    let impl_bar = Point { row: 4, column: 0 };
27501    let fn_new = Point { row: 5, column: 4 };
27502
27503    assert_eq!(sticky_headers(0.0), vec![]);
27504    assert_eq!(sticky_headers(0.5), vec![(fn_foo, 0.0)]);
27505    assert_eq!(sticky_headers(1.0), vec![(fn_foo, 0.0)]);
27506    assert_eq!(sticky_headers(1.5), vec![(fn_foo, -0.5)]);
27507    assert_eq!(sticky_headers(2.0), vec![]);
27508    assert_eq!(sticky_headers(2.5), vec![]);
27509    assert_eq!(sticky_headers(3.0), vec![]);
27510    assert_eq!(sticky_headers(3.5), vec![]);
27511    assert_eq!(sticky_headers(4.0), vec![]);
27512    assert_eq!(sticky_headers(4.5), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
27513    assert_eq!(sticky_headers(5.0), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
27514    assert_eq!(sticky_headers(5.5), vec![(impl_bar, 0.0), (fn_new, 0.5)]);
27515    assert_eq!(sticky_headers(6.0), vec![(impl_bar, 0.0)]);
27516    assert_eq!(sticky_headers(6.5), vec![(impl_bar, 0.0)]);
27517    assert_eq!(sticky_headers(7.0), vec![(impl_bar, 0.0)]);
27518    assert_eq!(sticky_headers(7.5), vec![(impl_bar, -0.5)]);
27519    assert_eq!(sticky_headers(8.0), vec![]);
27520    assert_eq!(sticky_headers(8.5), vec![]);
27521    assert_eq!(sticky_headers(9.0), vec![]);
27522    assert_eq!(sticky_headers(9.5), vec![]);
27523    assert_eq!(sticky_headers(10.0), vec![]);
27524}
27525
27526#[gpui::test]
27527async fn test_scroll_by_clicking_sticky_header(cx: &mut TestAppContext) {
27528    init_test(cx, |_| {});
27529    cx.update(|cx| {
27530        SettingsStore::update_global(cx, |store, cx| {
27531            store.update_user_settings(cx, |settings| {
27532                settings.editor.sticky_scroll = Some(settings::StickyScrollContent {
27533                    enabled: Some(true),
27534                })
27535            });
27536        });
27537    });
27538    let mut cx = EditorTestContext::new(cx).await;
27539
27540    let line_height = cx.editor(|editor, window, _cx| {
27541        editor
27542            .style()
27543            .unwrap()
27544            .text
27545            .line_height_in_pixels(window.rem_size())
27546    });
27547
27548    let buffer = indoc! {"
27549            ˇfn foo() {
27550                let abc = 123;
27551            }
27552            struct Bar;
27553            impl Bar {
27554                fn new() -> Self {
27555                    Self
27556                }
27557            }
27558            fn baz() {
27559            }
27560        "};
27561    cx.set_state(&buffer);
27562
27563    cx.update_editor(|e, _, cx| {
27564        e.buffer()
27565            .read(cx)
27566            .as_singleton()
27567            .unwrap()
27568            .update(cx, |buffer, cx| {
27569                buffer.set_language(Some(rust_lang()), cx);
27570            })
27571    });
27572
27573    let fn_foo = || empty_range(0, 0);
27574    let impl_bar = || empty_range(4, 0);
27575    let fn_new = || empty_range(5, 4);
27576
27577    let mut scroll_and_click = |scroll_offset: ScrollOffset, click_offset: ScrollOffset| {
27578        cx.update_editor(|e, window, cx| {
27579            e.scroll(
27580                gpui::Point {
27581                    x: 0.,
27582                    y: scroll_offset,
27583                },
27584                None,
27585                window,
27586                cx,
27587            );
27588        });
27589        cx.simulate_click(
27590            gpui::Point {
27591                x: px(0.),
27592                y: click_offset as f32 * line_height,
27593            },
27594            Modifiers::none(),
27595        );
27596        cx.update_editor(|e, _, cx| (e.scroll_position(cx), display_ranges(e, cx)))
27597    };
27598
27599    assert_eq!(
27600        scroll_and_click(
27601            4.5, // impl Bar is halfway off the screen
27602            0.0  // click top of screen
27603        ),
27604        // scrolled to impl Bar
27605        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
27606    );
27607
27608    assert_eq!(
27609        scroll_and_click(
27610            4.5,  // impl Bar is halfway off the screen
27611            0.25  // click middle of impl Bar
27612        ),
27613        // scrolled to impl Bar
27614        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
27615    );
27616
27617    assert_eq!(
27618        scroll_and_click(
27619            4.5, // impl Bar is halfway off the screen
27620            1.5  // click below impl Bar (e.g. fn new())
27621        ),
27622        // scrolled to fn new() - this is below the impl Bar header which has persisted
27623        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
27624    );
27625
27626    assert_eq!(
27627        scroll_and_click(
27628            5.5,  // fn new is halfway underneath impl Bar
27629            0.75  // click on the overlap of impl Bar and fn new()
27630        ),
27631        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
27632    );
27633
27634    assert_eq!(
27635        scroll_and_click(
27636            5.5,  // fn new is halfway underneath impl Bar
27637            1.25  // click on the visible part of fn new()
27638        ),
27639        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
27640    );
27641
27642    assert_eq!(
27643        scroll_and_click(
27644            1.5, // fn foo is halfway off the screen
27645            0.0  // click top of screen
27646        ),
27647        (gpui::Point { x: 0., y: 0. }, vec![fn_foo()])
27648    );
27649
27650    assert_eq!(
27651        scroll_and_click(
27652            1.5,  // fn foo is halfway off the screen
27653            0.75  // click visible part of let abc...
27654        )
27655        .0,
27656        // no change in scroll
27657        // we don't assert on the visible_range because if we clicked the gutter, our line is fully selected
27658        (gpui::Point { x: 0., y: 1.5 })
27659    );
27660}
27661
27662#[gpui::test]
27663async fn test_next_prev_reference(cx: &mut TestAppContext) {
27664    const CYCLE_POSITIONS: &[&'static str] = &[
27665        indoc! {"
27666            fn foo() {
27667                let ˇabc = 123;
27668                let x = abc + 1;
27669                let y = abc + 2;
27670                let z = abc + 2;
27671            }
27672        "},
27673        indoc! {"
27674            fn foo() {
27675                let abc = 123;
27676                let x = ˇabc + 1;
27677                let y = abc + 2;
27678                let z = abc + 2;
27679            }
27680        "},
27681        indoc! {"
27682            fn foo() {
27683                let abc = 123;
27684                let x = abc + 1;
27685                let y = ˇabc + 2;
27686                let z = abc + 2;
27687            }
27688        "},
27689        indoc! {"
27690            fn foo() {
27691                let abc = 123;
27692                let x = abc + 1;
27693                let y = abc + 2;
27694                let z = ˇabc + 2;
27695            }
27696        "},
27697    ];
27698
27699    init_test(cx, |_| {});
27700
27701    let mut cx = EditorLspTestContext::new_rust(
27702        lsp::ServerCapabilities {
27703            references_provider: Some(lsp::OneOf::Left(true)),
27704            ..Default::default()
27705        },
27706        cx,
27707    )
27708    .await;
27709
27710    // importantly, the cursor is in the middle
27711    cx.set_state(indoc! {"
27712        fn foo() {
27713            let aˇbc = 123;
27714            let x = abc + 1;
27715            let y = abc + 2;
27716            let z = abc + 2;
27717        }
27718    "});
27719
27720    let reference_ranges = [
27721        lsp::Position::new(1, 8),
27722        lsp::Position::new(2, 12),
27723        lsp::Position::new(3, 12),
27724        lsp::Position::new(4, 12),
27725    ]
27726    .map(|start| lsp::Range::new(start, lsp::Position::new(start.line, start.character + 3)));
27727
27728    cx.lsp
27729        .set_request_handler::<lsp::request::References, _, _>(move |params, _cx| async move {
27730            Ok(Some(
27731                reference_ranges
27732                    .map(|range| lsp::Location {
27733                        uri: params.text_document_position.text_document.uri.clone(),
27734                        range,
27735                    })
27736                    .to_vec(),
27737            ))
27738        });
27739
27740    let _move = async |direction, count, cx: &mut EditorLspTestContext| {
27741        cx.update_editor(|editor, window, cx| {
27742            editor.go_to_reference_before_or_after_position(direction, count, window, cx)
27743        })
27744        .unwrap()
27745        .await
27746        .unwrap()
27747    };
27748
27749    _move(Direction::Next, 1, &mut cx).await;
27750    cx.assert_editor_state(CYCLE_POSITIONS[1]);
27751
27752    _move(Direction::Next, 1, &mut cx).await;
27753    cx.assert_editor_state(CYCLE_POSITIONS[2]);
27754
27755    _move(Direction::Next, 1, &mut cx).await;
27756    cx.assert_editor_state(CYCLE_POSITIONS[3]);
27757
27758    // loops back to the start
27759    _move(Direction::Next, 1, &mut cx).await;
27760    cx.assert_editor_state(CYCLE_POSITIONS[0]);
27761
27762    // loops back to the end
27763    _move(Direction::Prev, 1, &mut cx).await;
27764    cx.assert_editor_state(CYCLE_POSITIONS[3]);
27765
27766    _move(Direction::Prev, 1, &mut cx).await;
27767    cx.assert_editor_state(CYCLE_POSITIONS[2]);
27768
27769    _move(Direction::Prev, 1, &mut cx).await;
27770    cx.assert_editor_state(CYCLE_POSITIONS[1]);
27771
27772    _move(Direction::Prev, 1, &mut cx).await;
27773    cx.assert_editor_state(CYCLE_POSITIONS[0]);
27774
27775    _move(Direction::Next, 3, &mut cx).await;
27776    cx.assert_editor_state(CYCLE_POSITIONS[3]);
27777
27778    _move(Direction::Prev, 2, &mut cx).await;
27779    cx.assert_editor_state(CYCLE_POSITIONS[1]);
27780}
27781
27782#[gpui::test]
27783async fn test_multibuffer_selections_with_folding(cx: &mut TestAppContext) {
27784    init_test(cx, |_| {});
27785
27786    let (editor, cx) = cx.add_window_view(|window, cx| {
27787        let multi_buffer = MultiBuffer::build_multi(
27788            [
27789                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
27790                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
27791            ],
27792            cx,
27793        );
27794        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
27795    });
27796
27797    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
27798    let buffer_ids = cx.multibuffer(|mb, _| mb.excerpt_buffer_ids());
27799
27800    cx.assert_excerpts_with_selections(indoc! {"
27801        [EXCERPT]
27802        ˇ1
27803        2
27804        3
27805        [EXCERPT]
27806        1
27807        2
27808        3
27809        "});
27810
27811    // Scenario 1: Unfolded buffers, position cursor on "2", select all matches, then insert
27812    cx.update_editor(|editor, window, cx| {
27813        editor.change_selections(None.into(), window, cx, |s| {
27814            s.select_ranges([2..3]);
27815        });
27816    });
27817    cx.assert_excerpts_with_selections(indoc! {"
27818        [EXCERPT]
27819        1
2782027821        3
27822        [EXCERPT]
27823        1
27824        2
27825        3
27826        "});
27827
27828    cx.update_editor(|editor, window, cx| {
27829        editor
27830            .select_all_matches(&SelectAllMatches, window, cx)
27831            .unwrap();
27832    });
27833    cx.assert_excerpts_with_selections(indoc! {"
27834        [EXCERPT]
27835        1
2783627837        3
27838        [EXCERPT]
27839        1
2784027841        3
27842        "});
27843
27844    cx.update_editor(|editor, window, cx| {
27845        editor.handle_input("X", window, cx);
27846    });
27847    cx.assert_excerpts_with_selections(indoc! {"
27848        [EXCERPT]
27849        1
2785027851        3
27852        [EXCERPT]
27853        1
2785427855        3
27856        "});
27857
27858    // Scenario 2: Select "2", then fold second buffer before insertion
27859    cx.update_multibuffer(|mb, cx| {
27860        for buffer_id in buffer_ids.iter() {
27861            let buffer = mb.buffer(*buffer_id).unwrap();
27862            buffer.update(cx, |buffer, cx| {
27863                buffer.edit([(0..buffer.len(), "1\n2\n3\n")], None, cx);
27864            });
27865        }
27866    });
27867
27868    // Select "2" and select all matches
27869    cx.update_editor(|editor, window, cx| {
27870        editor.change_selections(None.into(), window, cx, |s| {
27871            s.select_ranges([2..3]);
27872        });
27873        editor
27874            .select_all_matches(&SelectAllMatches, window, cx)
27875            .unwrap();
27876    });
27877
27878    // Fold second buffer - should remove selections from folded buffer
27879    cx.update_editor(|editor, _, cx| {
27880        editor.fold_buffer(buffer_ids[1], cx);
27881    });
27882    cx.assert_excerpts_with_selections(indoc! {"
27883        [EXCERPT]
27884        1
2788527886        3
27887        [EXCERPT]
27888        [FOLDED]
27889        "});
27890
27891    // Insert text - should only affect first buffer
27892    cx.update_editor(|editor, window, cx| {
27893        editor.handle_input("Y", window, cx);
27894    });
27895    cx.update_editor(|editor, _, cx| {
27896        editor.unfold_buffer(buffer_ids[1], cx);
27897    });
27898    cx.assert_excerpts_with_selections(indoc! {"
27899        [EXCERPT]
27900        1
2790127902        3
27903        [EXCERPT]
27904        1
27905        2
27906        3
27907        "});
27908
27909    // Scenario 3: Select "2", then fold first buffer before insertion
27910    cx.update_multibuffer(|mb, cx| {
27911        for buffer_id in buffer_ids.iter() {
27912            let buffer = mb.buffer(*buffer_id).unwrap();
27913            buffer.update(cx, |buffer, cx| {
27914                buffer.edit([(0..buffer.len(), "1\n2\n3\n")], None, cx);
27915            });
27916        }
27917    });
27918
27919    // Select "2" and select all matches
27920    cx.update_editor(|editor, window, cx| {
27921        editor.change_selections(None.into(), window, cx, |s| {
27922            s.select_ranges([2..3]);
27923        });
27924        editor
27925            .select_all_matches(&SelectAllMatches, window, cx)
27926            .unwrap();
27927    });
27928
27929    // Fold first buffer - should remove selections from folded buffer
27930    cx.update_editor(|editor, _, cx| {
27931        editor.fold_buffer(buffer_ids[0], cx);
27932    });
27933    cx.assert_excerpts_with_selections(indoc! {"
27934        [EXCERPT]
27935        [FOLDED]
27936        [EXCERPT]
27937        1
2793827939        3
27940        "});
27941
27942    // Insert text - should only affect second buffer
27943    cx.update_editor(|editor, window, cx| {
27944        editor.handle_input("Z", window, cx);
27945    });
27946    cx.update_editor(|editor, _, cx| {
27947        editor.unfold_buffer(buffer_ids[0], cx);
27948    });
27949    cx.assert_excerpts_with_selections(indoc! {"
27950        [EXCERPT]
27951        1
27952        2
27953        3
27954        [EXCERPT]
27955        1
2795627957        3
27958        "});
27959
27960    // Edge case scenario: fold all buffers, then try to insert
27961    cx.update_editor(|editor, _, cx| {
27962        editor.fold_buffer(buffer_ids[0], cx);
27963        editor.fold_buffer(buffer_ids[1], cx);
27964    });
27965    cx.assert_excerpts_with_selections(indoc! {"
27966        [EXCERPT]
27967        ˇ[FOLDED]
27968        [EXCERPT]
27969        [FOLDED]
27970        "});
27971
27972    // Insert should work via default selection
27973    cx.update_editor(|editor, window, cx| {
27974        editor.handle_input("W", window, cx);
27975    });
27976    cx.update_editor(|editor, _, cx| {
27977        editor.unfold_buffer(buffer_ids[0], cx);
27978        editor.unfold_buffer(buffer_ids[1], cx);
27979    });
27980    cx.assert_excerpts_with_selections(indoc! {"
27981        [EXCERPT]
27982        Wˇ1
27983        2
27984        3
27985        [EXCERPT]
27986        1
27987        Z
27988        3
27989        "});
27990}